예제 #1
0
    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
예제 #2
0
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)
예제 #4
0
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)
예제 #5
0
    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
예제 #6
0
    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)