matthewwithanm / pilkit

Utilities and processors built for, and on top of PIL
BSD 3-Clause "New" or "Revised" License
196 stars 54 forks source link

Image mode reset at the end of process method #34

Open fabiocaccamo opened 5 years ago

fabiocaccamo commented 5 years ago

Thank you for this great library.

I'm trying to convert a jpg image to black and white and store it in its original format, but I got the following error: IOError at ... cannot write mode RGBA as JPEG

The cause is that some processors need to change image mode to perform operations, but doesn't reset it to its init value at the end of the process.

I ended up writing my own filter to preserve image mode:

from pilkit.processors import Adjust

class BlackAndWhite(object):

    def __init__(self):
        self.processor = Adjust(color=0.0)

    def process(self, img):
        img_mode = img.mode
        img = self.processor.process(img)
        img = img.convert(img_mode)
        return img
kravemir commented 5 years ago

There was recently merged Convert processor in #32, which you can add to the end of a ProcessorPipeline, and convert image back to RGB mode.

fabiocaccamo commented 5 years ago

@kravemir Convert processor doesn't convert the image to its original format, the conversion should be handled internally and not by a processor.

Using a processor you will convert all images to the same format, the need is to convert each image to its original format after processing it.

kravemir commented 5 years ago

@kravemir Convert processor doesn't convert the image to its original format, ...

@fabiocaccamo I now get the difference. Sorry for the wrong suggestion.

So, you would need something like PreserveMode processor, or ConvertBackToOriginalMode processor, which wraps other processor (or, array of processors). Like, you have shown in yours example.

... the conversion should be handled internally and not by a processor.

What do you mean by "handled internally"? A processor, or a composition of processor using ProcessorPipeline, is called by process method to transform original image into a new image. And, a processor might return a different image object, than is the original one, as it depends on transformation, which is performed. So, in the end, the execution needs to be wrapped within some method, or processor.

fabiocaccamo commented 5 years ago

@kravemir exactly, this is how I actually solved the problem, here a custom processor example:

class BlackAndWhite(object):

    def __init__(self):
        self.processor = Adjust(color=0.0)

    def process(self, img):
        img_mode = img.mode
        img = self.processor.process(img)
        img = img.convert(img_mode)
        return img

I think that img_mode = img.mode and img = img.convert(img_mode) lines should be handled by the library to avoid duplicated code that solves always the same problem.