def is_big_png_photo(self) -> bool: img = Image.open(self.src_path) orig_format = img.format orig_mode = img.mode if orig_format != 'PNG' or orig_mode in ['P', 'L', 'LA']: return False w, h = img.size if (w * h) >= MIN_BIG_IMG_AREA: unique_colors = { img.getpixel((x, y)) for x in range(w) for y in range(h) } if len(unique_colors) > 2**16: img = img.convert("RGB") if w > h: img, status = downsize_img(img, 1600, 0) else: img, status = downsize_img(img, 0, 1600) tempfile = BytesIO() try: img.save(tempfile, quality=80, format="JPEG") except IOError: ImageFile.MAXBLOCK = img.size[0] * img.size[1] img.save(tempfile, quality=80, format="JPEG") final_size = tempfile.getbuffer().nbytes return final_size > MIN_BIG_IMG_SIZE return False
def is_big_png_photo(src_path: str) -> bool: """Try to determine if a given image if a big photo in PNG format Expects a path to a PNG image file. Returns True if the image is a PNG with an area bigger than MIN_BIG_IMG_AREA pixels that when resized to 1600 pixels (wide or high) converts to a JPEG bigger than MIN_BIG_IMG_SIZE. Returns False otherwise. Inspired by an idea first presented by Stephen Arthur (https://engineeringblog.yelp.com/2017/06/making-photos-smaller.html) """ img = Image.open(src_path) orig_format = img.format orig_mode = img.mode if orig_format != 'PNG' or orig_mode in ['P', 'L', 'LA']: return False w, h = img.size if (w * h) >= MIN_BIG_IMG_AREA: unique_colors = { img.getpixel((x, y)) for x in range(w) for y in range(h) } if len(unique_colors) > 2**16: img = img.convert("RGB") if w > h: img, status = downsize_img(img, 1600, 0) else: img, status = downsize_img(img, 0, 1600) tempfile = BytesIO() try: img.save(tempfile, quality=80, format="JPEG") except IOError: ImageFile.MAXBLOCK = img.size[0] * img.size[1] img.save(tempfile, quality=80, format="JPEG") final_size = tempfile.getbuffer().nbytes return final_size > MIN_BIG_IMG_SIZE return False
def optimize_png(t: Task) -> TaskResult: """ Try to reduce file size of a PNG image. Expects a Task object containing all the parameters for the image processing. If file reduction is successful, this function will replace the original file with the optimized version and return some report data (file path, image format, image color mode, original file size, resulting file size, and resulting status of the optimization. :param t: A Task object containing all the parameters for the image processing. :return: A TaskResult object containing information for single file report. """ img = Image.open(t.src_path) orig_format = img.format orig_mode = img.mode folder, filename = os.path.split(t.src_path) temp_file_path = os.path.join(folder + "/~temp~" + filename) orig_size = os.path.getsize(t.src_path) orig_colors, final_colors = 0, 0 had_exif = has_exif = False # Currently no exif methods for PNG files if orig_mode == 'P': final_colors = orig_colors = len(img.getcolors()) if t.convert_all or (t.conv_big and is_big_png_photo(t.src_path)): # convert to jpg format filename = os.path.splitext(os.path.basename(t.src_path))[0] conv_file_path = os.path.join(folder + "/" + filename + ".jpg") if t.max_w or t.max_h: img, was_downsized = downsize_img(img, t.max_w, t.max_h) else: was_downsized = False img = remove_transparency(img, t.bg_color) img = img.convert("RGB") if t.grayscale: img = make_grayscale(img) try: img.save(conv_file_path, quality=t.quality, optimize=True, progressive=True, format="JPEG") except IOError: ImageFile.MAXBLOCK = img.size[0] * img.size[1] img.save(conv_file_path, quality=t.quality, optimize=True, progressive=True, format="JPEG") # Only save the converted file if conversion did save any space final_size = os.path.getsize(conv_file_path) if t.no_size_comparison or (orig_size - final_size > 0): was_optimized = True if t.force_del: try: os.remove(t.src_path) except OSError as e: msg = "Error while replacing original PNG with the " \ "new JPEG version." print(f"\n{msg}\n{e}\n") else: final_size = orig_size was_optimized = False try: os.remove(conv_file_path) except OSError as e: msg = "Error while removing temporary JPEG converted file." print(f"\n{msg}\n{e}\n") result_format = "JPEG" return TaskResult(t.src_path, orig_format, result_format, orig_mode, img.mode, orig_colors, final_colors, orig_size, final_size, was_optimized, was_downsized, had_exif, has_exif) # if PNG and user didn't ask for PNG to JPEG conversion, do this instead. else: result_format = "PNG" if t.remove_transparency: img = remove_transparency(img, t.bg_color) if t.max_w or t.max_h: img, was_downsized = downsize_img(img, t.max_w, t.max_h) else: was_downsized = False if t.reduce_colors: img, orig_colors, final_colors = do_reduce_colors( img, t.max_colors) if t.grayscale: img = make_grayscale(img) if not t.fast_mode and img.mode == "P": img, final_colors = rebuild_palette(img) try: img.save(temp_file_path, optimize=True, format=result_format) except IOError: ImageFile.MAXBLOCK = img.size[0] * img.size[1] img.save(temp_file_path, optimize=True, format=result_format) final_size = os.path.getsize(temp_file_path) # Only replace the original file if compression did save any space if t.no_size_comparison or (orig_size - final_size > 0): shutil.move(temp_file_path, os.path.expanduser(t.src_path)) was_optimized = True else: final_size = orig_size was_optimized = False try: os.remove(temp_file_path) except OSError as e: print(f"\nError while removing temporary file.\n{e}\n") return TaskResult(t.src_path, orig_format, result_format, orig_mode, img.mode, orig_colors, final_colors, orig_size, final_size, was_optimized, was_downsized, had_exif, has_exif)
def optimize_jpg(t: Task) -> TaskResult: """ Try to reduce file size of a JPG image. Expects a Task object containing all the parameters for the image processing. If file reduction is successful, this function will replace the original file with the optimized version and return some report data (file path, image format, image color mode, original file size, resulting file size, and resulting status of the optimization. :param t: A Task object containing all the parameters for the image processing. :return: A TaskResult object containing information for single file report. """ img = Image.open(t.src_path) orig_format = img.format orig_mode = img.mode folder, filename = os.path.split(t.src_path) if folder == '': folder = os.getcwd() temp_file_path = os.path.join(folder + "/~temp~" + filename) orig_size = os.path.getsize(t.src_path) orig_colors, final_colors = 0, 0 result_format = "JPEG" try: had_exif = True if piexif.load(t.src_path)['Exif'] else False except piexif.InvalidImageDataError: # Not a supported format had_exif = False except ValueError: # No exif info had_exif = False # TODO: Check if we can provide a more specific treatment of piexif exceptions. except Exception: had_exif = False if t.max_w or t.max_h: img, was_downsized = downsize_img(img, t.max_w, t.max_h) else: was_downsized = False if t.grayscale: img = make_grayscale(img) # only use progressive if file size is bigger use_progressive_jpg = orig_size > 10000 if t.fast_mode: quality = t.quality else: quality, jpgdiff = jpeg_dynamic_quality(img) try: img.save(temp_file_path, quality=quality, optimize=True, progressive=use_progressive_jpg, format=result_format) except IOError: ImageFile.MAXBLOCK = img.size[0] * img.size[1] img.save(temp_file_path, quality=quality, optimize=True, progressive=use_progressive_jpg, format=result_format) if t.keep_exif and had_exif: try: piexif.transplant(os.path.expanduser(t.src_path), temp_file_path) has_exif = True except ValueError: has_exif = False # TODO: Check if we can provide a more specific treatment of piexif exceptions. except Exception: had_exif = False else: has_exif = False # Only replace the original file if compression did save any space final_size = os.path.getsize(temp_file_path) if t.no_size_comparison or (orig_size - final_size > 0): shutil.move(temp_file_path, os.path.expanduser(t.src_path)) was_optimized = True else: final_size = orig_size was_optimized = False try: os.remove(temp_file_path) except OSError as e: details = 'Error while removing temporary file.' show_img_exception(e, t.src_path, details) return TaskResult(t.src_path, orig_format, result_format, orig_mode, img.mode, orig_colors, final_colors, orig_size, final_size, was_optimized, was_downsized, had_exif, has_exif)
def optimize_png(self): img = Image.open(self.src_path) orig_format = img.format orig_mode = img.mode folder, filename = os.path.split(self.src_path) if folder == '': folder = os.getcwd() temp_file_path = os.path.join(folder + "/~temp~" + filename) orig_size = os.path.getsize(self.src_path) orig_colors, final_colors = 0, 0 had_exif = has_exif = False # Currently no exif methods for PNG files if orig_mode == 'P': final_colors = orig_colors = len(img.getcolors()) if self.convert_all or (self.conv_big and self.is_big_png_photo()): # convert to jpg format filename = os.path.splitext(os.path.basename(self.src_path))[0] conv_file_path = os.path.join(folder + "/" + filename + ".jpg") if self.max_w or self.max_h: img, was_downsized = downsize_img(img, self.max_w, self.max_h) else: was_downsized = False img = remove_transparency(img, self.bg_color) img = img.convert("RGB") if self.grayscale: img = make_grayscale(img) try: img.save(conv_file_path, quality=self.quality, optimize=True, progressive=True, format="JPEG") except IOError: ImageFile.MAXBLOCK = img.size[0] * img.size[1] img.save(conv_file_path, quality=self.quality, optimize=True, progressive=True, format="JPEG") # Only save the converted file if conversion did save any space final_size = os.path.getsize(conv_file_path) if self.no_size_comparison or (orig_size - final_size > 0): was_optimized = True if self.force_del: try: os.remove(self.src_path) except OSError as e: details = 'Error while replacing original PNG with the new JPEG version.' show_img_exception(e, self.src_path, details) else: final_size = orig_size was_optimized = False try: os.remove(conv_file_path) except OSError as e: details = 'Error while removing temporary JPEG converted file.' show_img_exception(e, self.src_path, details) result_format = "JPEG" return self.src_path # if PNG and user didn't ask for PNG to JPEG conversion, do this instead. else: result_format = "PNG" if self.remove_transparency: img = remove_transparency(img, self.bg_color) if self.max_w or self.max_h: img, was_downsized = downsize_img(img, self.max_w, self.max_h) else: was_downsized = False if self.reduce_colors: img, orig_colors, final_colors = do_reduce_colors( img, self.max_colors) if self.grayscale: img = make_grayscale(img) if not self.fast_mode and img.mode == "P": img, final_colors = rebuild_palette(img) try: img.save(temp_file_path, optimize=True, format=result_format) except IOError: ImageFile.MAXBLOCK = img.size[0] * img.size[1] img.save(temp_file_path, optimize=True, format=result_format) final_size = os.path.getsize(temp_file_path) # Only replace the original file if compression did save any space if self.no_size_comparison or (orig_size - final_size > 0): shutil.move(temp_file_path, os.path.expanduser(self.src_path)) was_optimized = True else: final_size = orig_size was_optimized = False try: os.remove(temp_file_path) except OSError as e: details = 'Error while removing temporary file.' show_img_exception(e, self.src_path, details) return self.src_path
def optimize_jpg(self): try: img = Image.open(self.src_path) orig_format = img.format orig_mode = img.mode folder, filename = os.path.split(self.src_path) if folder == '': folder = os.getcwd() temp_file_path = os.path.join(folder + "/~temp~" + filename) orig_size = os.path.getsize(self.src_path) orig_colors, final_colors = 0, 0 result_format = "JPEG" try: had_exif = True if piexif.load( self.src_path)['Exif'] else False except piexif.InvalidImageDataError: # Not a supported format had_exif = False except ValueError: # No exif info had_exif = False # TODO: Check if we can provide a more specific treatment of piexif exceptions. except Exception: had_exif = False if self.max_w or self.max_h: img, was_downsized = downsize_img(img, self.max_w, self.max_h) else: was_downsized = False if self.grayscale: img = make_grayscale(img) # only use progressive if file size is bigger use_progressive_jpg = orig_size > 10000 if self.fast_mode: quality = self.quality else: quality, jpgdiff = self.jpeg_dynamic_quality(img) try: img.save(temp_file_path, quality=quality, optimize=True, progressive=use_progressive_jpg, format=result_format) except IOError: ImageFile.MAXBLOCK = img.size[0] * img.size[1] img.save(temp_file_path, quality=quality, optimize=True, progressive=use_progressive_jpg, format=result_format) if self.keep_exif and had_exif: try: piexif.transplant(os.path.expanduser(self.src_path), temp_file_path) has_exif = True except ValueError: has_exif = False # TODO: Check if we can provide a more specific treatment of piexif exceptions. except Exception: had_exif = False else: has_exif = False # Only replace the original file if compression did save any space final_size = os.path.getsize(temp_file_path) if self.no_size_comparison or (orig_size - final_size > 0): shutil.move(temp_file_path, os.path.expanduser(self.src_path)) was_optimized = True else: final_size = orig_size was_optimized = False try: os.remove(temp_file_path) except OSError as e: details = 'Error while removing temporary file.' show_img_exception(e, self.src_path, details) return self.src_path except Exception as e: print(e)
def optimize_png(task: Task) -> TaskResult: """ Try to reduce file size of a PNG image. Expects a Task object containing all the parameters for the image processing. If file reduction is successful, this function will replace the original file with the optimized version and return some report data (file path, image format, image color mode, original file size, resulting file size, and resulting status of the optimization. :param task: A Task object containing all the parameters for the image processing. :return: A TaskResult object containing information for single file report. """ img: Image.Image = Image.open(task.src_path) orig_format = img.format orig_mode = img.mode folder, filename = os.path.split(task.src_path) if folder == '': folder = os.getcwd() orig_size = os.path.getsize(task.src_path) orig_colors, final_colors = 0, 0 had_exif = has_exif = False # Currently no exif methods for PNG files if orig_mode == 'P': final_colors = orig_colors = len(img.getcolors()) if task.convert_all or (task.conv_big and is_big_png_photo(task.src_path)): # convert to jpg format filename = os.path.splitext(os.path.basename(task.src_path))[0] output_path = os.path.join(folder + "/" + filename + ".jpg") if task.max_w or task.max_h: img, was_downsized = downsize_img(img, task.max_w, task.max_h) else: was_downsized = False img = remove_transparency(img, task.bg_color) img = img.convert("RGB") if task.grayscale: img = make_grayscale(img) tmp_buffer = BytesIO() # In-memory buffer try: img.save( tmp_buffer, quality=task.quality, optimize=True, progressive=True, format="JPEG") except IOError: ImageFile.MAXBLOCK = img.size[0] * img.size[1] img.save( tmp_buffer, quality=task.quality, optimize=True, progressive=True, format="JPEG") img_mode = img.mode img.close() compare_sizes = not (task.no_size_comparison or task.convert_all) was_optimized, final_size = save_compressed(task.src_path, tmp_buffer, force_delete=task.force_del, compare_sizes=compare_sizes, output_path=output_path) result_format = "JPEG" return TaskResult(task.src_path, orig_format, result_format, orig_mode, img_mode, orig_colors, final_colors, orig_size, final_size, was_optimized, was_downsized, had_exif, has_exif) # if PNG and user didn't ask for PNG to JPEG conversion, do this instead. else: result_format = "PNG" if task.remove_transparency: img = remove_transparency(img, task.bg_color) if task.max_w or task.max_h: img, was_downsized = downsize_img(img, task.max_w, task.max_h) else: was_downsized = False if task.reduce_colors: img, orig_colors, final_colors = do_reduce_colors( img, task.max_colors) if task.grayscale: img = make_grayscale(img) if not task.fast_mode and img.mode == "P": img, final_colors = rebuild_palette(img) tmp_buffer = BytesIO() # In-memory buffer try: img.save(tmp_buffer, optimize=True, format=result_format) except IOError: ImageFile.MAXBLOCK = img.size[0] * img.size[1] img.save(tmp_buffer, optimize=True, format=result_format) img_mode = img.mode img.close() compare_sizes = not task.no_size_comparison was_optimized, final_size = save_compressed(task.src_path, tmp_buffer, force_delete=task.force_del, compare_sizes=compare_sizes) return TaskResult(task.src_path, orig_format, result_format, orig_mode, img_mode, orig_colors, final_colors, orig_size, final_size, was_optimized, was_downsized, had_exif, has_exif)