def renderCaptcha(self, cpath='', solpath='', cname=None): ''' Generate one captcha set (captcha image + solution mask) ''' # == INIT # Position the reference image (clickable model-only image) to the left or to the right? (this adds more complexity for bots) rightorleft = random.random() # Generate a random name, unless it was specified otherwise if not cname: cname = "%s" % self.randomID() # Generating a 3D scene w = self.world w.generateScene() # == GENERATING CAPTCHA SOLUTION MASK # Render and save clickable mask #w.renderClickableArea() # Already rendered just after the generation of the world imgMask = w.renderToPNM() if rightorleft < 0.5: imgMask.expandBorder(left=imgMask.getReadXSize(), right=0, bottom=0, top=0, color=(0,0,0,1)) else: imgMask.expandBorder(left=0, right=imgMask.getReadXSize(), bottom=0, top=0, color=(0,0,0,1)) # Save the solution mask as a PNG then RLE # TODO find a way to convert from PNMImage to RLE directly in-memory without saving it to a file pngpath = os.path.join(cpath, "%s.png" % cname) imgMask.write(pngpath) # save image as png to avoid losing information (important since it's the solution mask!) imgMask.clear() # freeing memory #w.renderAndSave('testmask.png') # Convert PNG to RLE rle = RLEImage() rle.convertImageToRLE(pngpath, os.path.join(cpath, "%s.rle" % cname)) del rle # freeing memory if not self.debug and not self.debugPng: os.remove(pngpath) # delete the png, not needed and it frees some space # == GENERATING CAPTCHA QUESTION # Render and save captcha image (the one that will be presented to the user) # First we generate the whole captcha scene (where the user will have to click) w.renderReset() img1 = w.renderToPNM() # Second, we generate the reference model alone so that the user know what he has to look for w.renderClickableModel(self.showRefModel, self.showRefName) img2 = w.renderToPNM() # Place the reference image randomly at the right side or at the left side (to confuse bots a little bit more) if rightorleft < 0.5: img1.expandBorder(left=img2.getReadXSize(), right=0, bottom=0, top=0, color=(0,0,0,1)) # resizing the first image to the size of two images (reference image + whole scene image) img1.copySubImage(img2, 0, 0) # copying reference image onto the first one, side by side else: img1.expandBorder(left=0, right=img2.getReadXSize(), bottom=0, top=0, color=(0,0,0,1)) img1.copySubImage(img2, img2.getReadXSize(), 0) # Saving final image # First step: we save the jpg image jpgpath = os.path.join(solpath, "%s.jpg" % cname) img1.write( jpgpath ) # save image (using jpg will add even more complexity for bots, because JPEG is a lossy image compression using psycho-human optimization: lossy + adapted to human = worst for bots) TODO: add a parameter to adjust the JPEG quality and loss img2.clear() # freeing memory img1.clear() # freeing memory # Second step: reload the jpg in PIL and resave with a different quality (because Panda3D doesn't allow to set jpg quality) # This is important: saving with a lower quality reduce bandwidth but also raise the difficulty for the bots img = Image.open(jpgpath, 'r') img.save(jpgpath, quality = self.jpgquality, optimize=False, progressive=True) # Optimize does an extra pass. Not needed here: we don't want to optimize for bots. del img # freeing memory if self.debug: w.base.run() # if debug mode enabled, we show a window with the world we have just generated return cname # return the ID of the generated captcha
def checkSolution(self, solpath, name, x, y): ''' Check if a Captcha response is correct ''' rle = RLEImage() result = rle.openAndCheck( os.path.join(solpath, "%s.rle" % name) , x, y) del rle return result