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),
        )
Example #2
0
    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
Example #3
0
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()
Example #4
0
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)
Example #5
0
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)
Example #8
0
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()
Example #9
0
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()
Example #10
0
    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
Example #11
0
    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
Example #12
0
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
Example #13
0
    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))
Example #14
0
    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
Example #15
0
    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)
Example #16
0
    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
Example #17
0
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