def test_jpeg(self): path = os.path.join(TEST_DATA_PATH, "Sam_Hat1.jpg") image = Image.objects.create_from_path(path) # Re-load the image, now that the task is done image = Image.objects.get(id=image.id) self.assertTrue(image.source.path.endswith("Sam_Hat1.jpg")) self.assertEqual(image.width, 3264) self.assertEqual(image.height, 2448) self.assertEqual(image.jpeg_quality, None) self.assertTrue(os.path.exists(image.optimized.path)) self.assertTrue(os.path.exists(image.source.path)) source = PILImage.open(image.source.path) optimized = PILImage.open(image.optimized.path) self.assertEqual( source.quantization, optimized.quantization ) self.assertEqual( JpegImagePlugin.get_sampling(source), JpegImagePlugin.get_sampling(optimized), )
def create_image(self, buffer): try: img = Image.open(BytesIO(buffer)) except DECOMPRESSION_BOMB_EXCEPTIONS as error: logger.warning("[PILEngine] create_image failed: %s", error) return None self.icc_profile = img.info.get("icc_profile") self.exif = img.info.get("exif") self.original_mode = img.mode self.subsampling = JpegImagePlugin.get_sampling(img) if self.subsampling == -1: # n/a for this file self.subsampling = None self.qtables = getattr(img, "quantization", None) if (self.context.config.ALLOW_ANIMATED_GIFS and self.extension == ".gif"): frames = [] for frame in ImageSequence.Iterator(img): frames.append(frame.convert("P")) img.seek(0) self.frame_count = len(frames) return frames return img
def optimize_image(image_model, image_buffer, filename): im = PILImage.open(image_buffer) # Let's cache some important stuff format = im.format icc_profile = im.info.get("icc_profile") quantization = getattr(im, "quantization", None) subsampling = None if format == "JPEG": try: subsampling = JpegImagePlugin.get_sampling(im) except IndexError: # Ignore if sampling fails logger.debug('JPEG sampling failed, ignoring') except: # mparent(2016-03-25): Eventually eliminate "catch all", but need to log errors to see # if we're missing any other exception types in the wild logger.exception('JPEG sampling error') if im.size[0] > settings.BETTY_MAX_WIDTH: # If the image is really large, we'll save a more reasonable version as the "original" height = settings.BETTY_MAX_WIDTH * float(im.size[1]) / float(im.size[0]) im = im.resize((settings.BETTY_MAX_WIDTH, int(round(height))), PILImage.ANTIALIAS) out_buffer = io.BytesIO() if format == "JPEG" and im.mode == "RGB": # For JPEG files, we need to make sure that we keep the quantization profile try: im.save( out_buffer, icc_profile=icc_profile, qtables=quantization, subsampling=subsampling, format="JPEG") except ValueError as e: # Maybe the image already had an invalid quant table? if e.args[:1] == ('Invalid quantization table',): out_buffer = io.BytesIO() # Make sure it's empty after failed save attempt im.save( out_buffer, icc_profile=icc_profile, format=format, ) else: raise else: im.save(out_buffer, icc_profile=icc_profile, format=format) image_model.optimized.save(filename, File(out_buffer)) else: # No modifications, just save original as optimized image_buffer.seek(0) image_model.optimized.save(filename, File(image_buffer)) image_model.save()
def save_image(img, fname): print(fname) quantization = getattr(img, 'quantization', None) subsampling = JpegImagePlugin.get_sampling(img) if quantization else None if subsampling: img.save(fname, subsampling=subsampling, qtables=quantization) else: img.save(fname)
def optimize_image(image): im = PILImage.open(image.source.path) # Let's cache some important stuff format = im.format icc_profile = im.info.get("icc_profile") quantization = getattr(im, "quantization", None) subsampling = None if format == "JPEG": try: subsampling = JpegImagePlugin.get_sampling(im) except: pass # Sometimes, crazy images exist. filename = os.path.split(image.source.path)[1] if im.size[0] > settings.BETTY_MAX_WIDTH: # If the image is really large, we'll save a more reasonable version as the "original" height = settings.BETTY_MAX_WIDTH * float(im.size[1]) / float(im.size[0]) im = im.resize((settings.BETTY_MAX_WIDTH, int(round(height))), PILImage.ANTIALIAS) """OK, so this suuuuuucks. When we convert or resize an Image, it is no longer a JPEG. So, in order to reset the quanitzation, etc, we need to save this to a file and then re-read it from the filesystem. Silly, I know. Once my pull request is approved, this can be removed, and we can just pass the qtables into the save method. PR is here: https://github.com/python-imaging/Pillow/pull/677 """ temp = tempfile.NamedTemporaryFile() im.save(temp, format="JPEG") temp.seek(0) im = PILImage.open(temp) im.quantization = quantization image.optimized.name = optimized_upload_to(image, filename) if format == "JPEG" and im.mode == "RGB": # For JPEG files, we need to make sure that we keep the quantization profile try: im.save( image.optimized.name, icc_profile=icc_profile, quality="keep", subsampling=subsampling) except (TypeError, ValueError) as e: # Maybe the image already had an invalid quant table? if e.message.startswith("Not a valid numbers of quantization tables"): im.save( image.optimized.name, icc_profile=icc_profile ) else: raise else: im.save(image.optimized.name, icc_profile=icc_profile) image.save()
def test_jpeg(): shutil.rmtree(bettysettings.BETTY_IMAGE_ROOT, ignore_errors=True) path = os.path.join(TEST_DATA_PATH, "Sam_Hat1.jpg") image = Image.objects.create_from_path(path) # Re-load the image, now that the task is done image = Image.objects.get(id=image.id) assert image.source.path.endswith("Sam_Hat1.jpg") assert image.width == 3264 assert image.height == 2448 assert image.jpeg_quality is None assert os.path.exists(image.optimized.path) assert os.path.exists(image.source.path) source = PILImage.open(image.source.path) optimized = PILImage.open(image.optimized.path) assert source.quantization == optimized.quantization assert JpegImagePlugin.get_sampling(source) == JpegImagePlugin.get_sampling(optimized)
def test_jpeg(): path = os.path.join(TEST_DATA_PATH, "Sam_Hat1.jpg") image = Image.objects.create_from_path(path) # Re-load the image, now that the task is done image = Image.objects.get(id=image.id) assert image.source.path.endswith("Sam_Hat1.jpg") assert image.width == 3264 assert image.height == 2448 assert image.jpeg_quality is None assert os.path.exists(image.optimized.path) assert os.path.exists(image.source.path) assert not image.animated source = PILImage.open(image.source.path) optimized = PILImage.open(image.optimized.path) assert source.quantization == optimized.quantization assert JpegImagePlugin.get_sampling(source) == JpegImagePlugin.get_sampling(optimized)
def optimize_image(image): im = PILImage.open(image.source.path) # Let's cache some important stuff format = im.format icc_profile = im.info.get("icc_profile") quantization = getattr(im, "quantization", None) subsampling = None if format == "JPEG": try: subsampling = JpegImagePlugin.get_sampling(im) except: pass # Sometimes, crazy images exist. filename = os.path.split(image.source.path)[1] image.optimized.name = optimized_upload_to(image, filename) if im.size[0] > settings.BETTY_MAX_WIDTH: # If the image is really large, we'll save a more reasonable version as the "original" height = settings.BETTY_MAX_WIDTH * float(im.size[1]) / float(im.size[0]) im = im.resize((settings.BETTY_MAX_WIDTH, int(round(height))), PILImage.ANTIALIAS) if format == "JPEG" and im.mode == "RGB": # For JPEG files, we need to make sure that we keep the quantization profile try: im.save( image.optimized.name, icc_profile=icc_profile, qtables=quantization, subsampling=subsampling, format="JPEG") except (TypeError, ValueError) as e: # Maybe the image already had an invalid quant table? if e.message.startswith("Not a valid numbers of quantization tables"): im.save( image.optimized.name, icc_profile=icc_profile ) else: raise else: im.save(image.optimized.name, icc_profile=icc_profile) else: shutil.copy2(image.source.path, image.optimized.name) image.save()
def optimize_image(image): im = PILImage.open(image.source.path) # Let's cache some important stuff format = im.format icc_profile = im.info.get("icc_profile") quantization = getattr(im, "quantization", None) subsampling = None if format == "JPEG": try: subsampling = JpegImagePlugin.get_sampling(im) except: pass # Sometimes, crazy images exist. filename = os.path.split(image.source.path)[1] if im.size[0] > settings.BETTY_MAX_WIDTH: # If the image is really large, we'll save a more reasonable version as the "original" height = settings.BETTY_MAX_WIDTH * float(im.size[1]) / float( im.size[0]) im = im.resize((settings.BETTY_MAX_WIDTH, int(round(height))), PILImage.ANTIALIAS) image.optimized.name = optimized_upload_to(image, filename) if format == "JPEG" and im.mode == "RGB": # For JPEG files, we need to make sure that we keep the quantization profile try: im.save(image.optimized.name, icc_profile=icc_profile, qtables=quantization, subsampling=subsampling, format="JPEG") except (TypeError, ValueError) as e: # Maybe the image already had an invalid quant table? if e.message.startswith( "Not a valid numbers of quantization tables"): im.save(image.optimized.name, icc_profile=icc_profile) else: raise else: im.save(image.optimized.name, icc_profile=icc_profile) image.save()
def create_image(self, buffer): img = Image.open(BytesIO(buffer)) self.icc_profile = img.info.get('icc_profile') self.transparency = img.info.get('transparency') self.exif = img.info.get('exif') self.subsampling = JpegImagePlugin.get_sampling(img) if (self.subsampling == -1): # n/a for this file self.subsampling = None self.qtables = getattr(img, 'quantization', None) if self.context.config.ALLOW_ANIMATED_GIFS and self.extension == '.gif': frames = [] for frame in ImageSequence.Iterator(img): frames.append(frame.convert('P')) img.seek(0) self.frame_count = len(frames) return frames return img
def resize_and_crop(org, dst, size, center=(0.5, 0.5)): from PIL import Image, JpegImagePlugin as JIP img = adjust_rotation(Image.open(org)) org_r = img.size[1] / img.size[0] dst_r = size[1] / size[0] s1 = (size[0], int(size[0] * org_r)) if org_r > dst_r else (int(size[1] / org_r), size[1]) d = [int((s1[i] - size[i]) / 2) for i in (0, 1)] c = [int((0.5 - center[i]) * s1[i]) for i in (0, 1)] i2 = img.resize(s1).crop( (d[0] - c[0], d[1] - c[1], s1[0] - d[0] - c[0], s1[1] - d[1] - c[1])) restore_rotation(i2.resize(size)).save(validate_path(dst), 'JPEG', optimize=True, exif=img.info['exif'], icc_profile=img.info.get( 'icc_profile', ''), subsampling=JIP.get_sampling(img)) return i2
def put_data_to_image(self, data): img = Image.new(self.image.mode, self.image.size) pixels = self.image.load() pixels_new = img.load() size_x, size_y = img.size data_pieces = (data[x:x+2] for x in range(0, len(data), 2)) for x in range(size_x): for y in range(size_y): r, g, b = pixels[x, y] r = bin(r)[2:] g = bin(g)[2:] b = bin(b)[2:] data_r = next(data_pieces, '') data_g = next(data_pieces, '') data_b = next(data_pieces, '') new_r = r[:-len(data_r) or len(r)] + data_r new_g = g[:-len(data_g) or len(g)] + data_g new_b = b[:-len(data_b) or len(b)] + data_b pixels_new[x, y] = (int(new_r, 2), int(new_g, 2), int(new_b, 2)) new_file_name = '.'.join(self.photo_path.split('.')[:-1]) + '_new.' + 'png' img.save(new_file_name, subsampling=JIP.get_sampling(img))
def create_image(self, buffer): try: img = Image.open(BytesIO(buffer)) except Image.DecompressionBombWarning as e: logger.warning("[PILEngine] create_image failed: {0}".format(e)) return None self.icc_profile = img.info.get('icc_profile') self.exif = img.info.get('exif') self.subsampling = JpegImagePlugin.get_sampling(img) if (self.subsampling == -1): # n/a for this file self.subsampling = None self.qtables = getattr(img, 'quantization', None) if self.context.config.ALLOW_ANIMATED_GIFS and self.extension == '.gif': frames = [] for frame in ImageSequence.Iterator(img): frames.append(frame.convert('P')) img.seek(0) self.frame_count = len(frames) return frames return img
def _make_page(self, args, filename): self.img = Image.open(filename) #self.img = change_resolution(self.img, [8.5, 11], 320, False) self.quantization = getattr(self.img, 'quantization', None) self.subsampling = JpegImagePlugin.get_sampling( self.img) if self.quantization else None if args.bg: bg = args.bg if bg.lower() == 'none': bg = None else: bg = detect_background_color(self.img) if args.scale != 1.0: print('Scaling image: {}'.format(args.scale)) self.img = self.img.resize( [int(i * args.scale) for i in self.img.size]) self.landscape = self.img.size[0] > self.img.size[1] if self.landscape: self.img = self.img.rotate(90, expand=True) im = image_to_array(self.img) threshold = args.threshold if not threshold: if Page.threshold is None: #Page.threshold, _ = auto_threshold(im) Page.threshold = np.mean(im) print('auto threshold:', Page.threshold) threshold = Page.threshold #panels = panelize_crop(im, threshold) #im = cv2.convertScaleAbs(im, alpha=2.5) panels, Page.kern_size, Page.iters = panelize_contours( im, threshold, Page.kern_size, Page.iters) return self._set_panels(args, self.img, panels, bg)
def create_image(self, buffer): try: img = Image.open(BytesIO(buffer)) except DecompressionBombExceptions as e: logger.warning("[PILEngine] create_image failed: {0}".format(e)) return None self.icc_profile = img.info.get('icc_profile') self.exif = img.info.get('exif') self.original_mode = img.mode self.subsampling = JpegImagePlugin.get_sampling(img) if (self.subsampling == -1): # n/a for this file self.subsampling = None self.qtables = getattr(img, 'quantization', None) if self.context.config.ALLOW_ANIMATED_GIFS and self.extension == '.gif': frames = [] for frame in ImageSequence.Iterator(img): frames.append(frame.convert('P')) img.seek(0) self.frame_count = len(frames) return frames return img
def one_image(path): if os.path.splitext(path.lower())[1] not in (".jpg", ".jpeg"): return 0 # a raw image may or may not exist, but we generally would want to keep any raw sync-ed up with its jpeg path_raw = os.path.splitext(path)[0] + ".cr2" if args.edit_in_place or args.dry_run: outpath = path outpath_raw = path_raw elif args.output_path: _, filepart = os.path.split(path) outpath = os.path.join(args.output_path, filepart) _, filepart = os.path.split(path_raw) outpath_raw = os.path.join(args.output_path, filepart) with Image.open(path) as imgobj: if 'exif' in imgobj.info: exif_dict = piexif.load(imgobj.info['exif']) else: print("Keys in image info: %s" % sorted(imgobj.info.keys())) raise Exception( "The file '%s' does not appear to contain exif data." % path) if args.filter_min_size and ( imgobj.size[0] <= args.filter_min_size[0] or imgobj.size[1] <= args.filter_min_size[1]): return 0 if args.filter_min_pixels and ( imgobj.size[0] * imgobj.size[1] <= args.filter_min_pixels[0] * args.filter_min_pixels[1]): return 0 if args.print_some_tags: print("Dims 1: %s x %s" % (exif_dict["0th"].get(piexif.ImageIFD.ImageWidth), exif_dict["0th"].get(piexif.ImageIFD.ImageLength))) print("Dims 2: %s x %s" % (exif_dict["Exif"].get(piexif.ExifIFD.PixelXDimension), exif_dict["Exif"].get(piexif.ExifIFD.PixelYDimension))) print("Date 1: %s" % parsedate2(exif_dict["0th"][piexif.ImageIFD.DateTime])) print( "Date 2: %s" % parsedate2(exif_dict["Exif"][piexif.ExifIFD.DateTimeOriginal])) print("Date 3: %s" % parsedate2( exif_dict["Exif"][piexif.ExifIFD.DateTimeDigitized])) if args.print_all_tags: for ifd in ("0th", "Exif", "GPS", "1st"): for tag in exif_dict[ifd]: val = exif_dict[ifd][tag] try: if len(val) > 100: val = "%s len %s" % (str(type(val)), len(val)) except TypeError: pass print("(%s) (%s) %s = %s" % (ifd, tag, piexif.TAGS[ifd][tag]["name"], val)) if args.adjust_date: changeto = parsedate(exif_dict["Exif"][ piexif.ExifIFD.DateTimeDigitized]) + datetime.timedelta( minutes=args.adjust_date) exif_dict["0th"][piexif.ImageIFD.DateTime] = changeto.strftime( "%Y:%m:%d %H:%M:%S") exif_dict["Exif"][ piexif.ExifIFD.DateTimeOriginal] = changeto.strftime( "%Y:%m:%d %H:%M:%S") exif_bytes = piexif.dump(exif_dict) shutil.copy2(path, args.temporary_file) compare_files(path, args.temporary_file, True) compare_images(path, args.temporary_file, True) piexif.insert(exif_bytes, args.temporary_file) compare_files(path, args.temporary_file, False) compare_images(path, args.temporary_file, True) if args.scale_percent: newdims = (int(imgobj.size[0] * args.scale_percent), int(imgobj.size[1] * args.scale_percent)) exif_dict["0th"][piexif.ImageIFD.ImageWidth] = newdims[0] exif_dict["0th"][piexif.ImageIFD.ImageLength] = newdims[ 1] # fix dims or they are wrong in exif exif_bytes = piexif.dump(exif_dict) quantization = getattr(imgobj, 'quantization', None) subsampling = JpegImagePlugin.get_sampling(imgobj) quality = 100 if quantization is None else 0 imgobj2 = imgobj.resize(newdims, resample=Image.LANCZOS) # include exif or else it is lost # also attempt to compress with the settings that were used previously imgobj2.save(args.temporary_file, exif=exif_bytes, format='jpeg', subsampling=subsampling, qtables=quantization, quality=quality) compare_files(path, args.temporary_file, False) compare_images(path, args.temporary_file, False) # the image object is closed here, now we can replace or delete the original jpeg if args.adjust_date or args.scale_percent: # we have theoretically made a temporary file with the contents we want, now we can get it where it needs to go if args.dry_run: print("Edit '%s'." % path) else: shutil.move(args.temporary_file, outpath) if os.path.exists(path_raw) and path_raw != outpath_raw: # the optional raw file is handled differently, as there is no modification and no temporary raw file if args.edit_in_place: shutil.move(path_raw, outpath_raw) elif args.output_path: shutil.copy2(path_raw, outpath_raw) else: print("Edit '%s'." % path_raw) if args.rename_images: changeto = parsedate(exif_dict["Exif"][ piexif.ExifIFD.DateTimeOriginal]).strftime("img_%Y%m%d_%H%M%S") outpath, filepart = os.path.split(outpath) extra = 0 finaloutpath = os.path.join(outpath, changeto + ".jpg") # deal with name conflicts finaloutpath_raw = os.path.join( outpath, changeto + ".cr2") # raw follows along if it exists while os.path.exists(finaloutpath) and path != finaloutpath: extra += 1 finaloutpath = os.path.join(outpath, "%s.%d.jpg" % (changeto, extra)) finaloutpath_raw = os.path.join(outpath, "%s.%d.cr2" % (changeto, extra)) if extra > 100: # because I don't trust unbounded loops raise Exception("Apparent runaway extra for %s." % path) if path == finaloutpath: return 0 if args.edit_in_place: func = shutil.move elif args.output_path: func = shutil.copy2 else: func = lambda x, y: print("Move '%s' to '%s'." % (x, y)) func(path, finaloutpath) if os.path.exists(path_raw): func(path_raw, finaloutpath_raw) return 1