Comments (3)
Hi @karelklic 👋
Thank you for reaching out and writing such an excellent ticket. I am curious about what kind of blur you are describing. I know that we had issues with state leakage once that caused low-res images to be upscaled.
Do you happen to have a sample image?
Other than that, I am usually hesitant to add too many settings. I believe this package should deliver the best possible results without a PhD in image processing.
Therefore I'd probably try to change the result to the best possible default for web imagery.
I tried to keep the image processing extendible, for or advanced use cases. That bit might be deserving of some better documentation though.
Ping me, about the sample image and will help to further investigate.
Cheers,
Joe
from django-pictures.
I investigated the issue further. The resampling filter is not causing the problem.
The root cause of blurriness is that WEBP produced by Pillow is lossy by default.
Therefore lossless PNG images become lossy WEBP thumbnails.
It would be better if Django-Pictures saved lossless WEBP when resizing PNG images. When a user chooses PNG instead of JPG, it's often because the image contains diagrams and infographics with lots of text that doesn't look well with lossy compression. The thumbnails then don't look well lossy-compressed too.
Here is a script that allows comparing different outputs from Pillow for reference:
#!/usr/bin/env python3
from PIL import Image
def create_thumbnail(image_path, thumbnail_path, thumbnail_size, resample, **kwargs):
# Open the original image.
image = Image.open(image_path)
# Create the thumbnail in-place.
image.thumbnail(thumbnail_size, resample=resample)
# Save the thumbnail.
image.save(thumbnail_path, **kwargs)
if __name__ == "__main__":
create_thumbnail("Original.png", "Thumbnail_bilinear_lossy.webp", (1000,500), Image.Resampling.BILINEAR, format="WEBP")
create_thumbnail("Original.png", "Thumbnail_bicubic_lossy.webp", (1000,500), Image.Resampling.BICUBIC, format="WEBP")
create_thumbnail("Original.png", "Thumbnail_lanczos_lossy.webp", (1000,500), Image.Resampling.LANCZOS, format="WEBP")
create_thumbnail("Original.png", "Thumbnail_box_lossy.webp", (1000,500), Image.Resampling.BOX, format="WEBP")
create_thumbnail("Original.png", "Thumbnail_hamming_lossy.webp", (1000,500), Image.Resampling.HAMMING, format="WEBP")
create_thumbnail("Original.png", "Thumbnail_bilinear_lossless.webp", (1000,500), Image.Resampling.BILINEAR, format="WEBP", lossless=True)
create_thumbnail("Original.png", "Thumbnail_bicubic_lossless.webp", (1000,500), Image.Resampling.BICUBIC, format="WEBP", lossless=True)
create_thumbnail("Original.png", "Thumbnail_lanczos_lossless.webp", (1000,500), Image.Resampling.LANCZOS, format="WEBP", lossless=True)
create_thumbnail("Original.png", "Thumbnail_box_lossless.webp", (1000,500), Image.Resampling.BOX, format="WEBP", lossless=True)
create_thumbnail("Original.png", "Thumbnail_hamming_lossless.webp", (1000,500), Image.Resampling.HAMMING, format="WEBP", lossless=True)
create_thumbnail("Original.png", "Thumbnail_bilinear.png", (1000,500), Image.Resampling.BILINEAR, format="PNG")
create_thumbnail("Original.png", "Thumbnail_bicubic.png", (1000,500), Image.Resampling.BICUBIC, format="PNG")
create_thumbnail("Original.png", "Thumbnail_lanczos.png", (1000,500), Image.Resampling.LANCZOS, format="PNG")
create_thumbnail("Original.png", "Thumbnail_box.png", (1000,500), Image.Resampling.BOX, format="PNG")
create_thumbnail("Original.png", "Thumbnail_hamming.png", (1000,500), Image.Resampling.HAMMING, format="PNG")
(I'm sending the sample original image and two thumbnails to your email for easy comparison.)
The solution could be to extend SimplePicture.save to distinguish between lossy and lossless format. I think it could be as simple as:
def save(self, image):
with io.BytesIO() as file_buffer:
img = self.process(image)
save_options = {}
if image.format == "PNG":
save_options = {"lossless": True}
img.save(file_buffer, format=self.file_type, **save_options)
...
Pillow documentation for Image.save states that unrecognized options are silently ignored, so the lossless
option can be provided regardless of self.file_type
value.
from django-pictures.
Hi @karelklic,
Thank you for your detailed response. I appreciate the effort in including a code sample to reproduce the problem.
I am hesitant to override Pillows default values. Those people usually know that they are doing, especially when it comes to tradeoffs between quality and size. Furthermore, I believe the assumption, that a user "knows" to use a PNG for looseless content questionable. Users have shown time and time again that Murphy's law remains solid.
However, giving you the developer the choice is important. Version 1.1 comes with a new setting that gives you the ability to swap the image processor. Beware, though, it's synchronous.
I am also not against adding exposing Pillow's Image.save
**params
via a setting. This setting could be per file type. We enable FILE_TYPES
to be a dictionary, like so:
# settings.py
PICTURES = {
# …
"FILE_TYPES": {
# Docs: https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html
"WEBP": {"lossless": True}
},
}
That being said, I don't think it will solve your particular issue, since it won't allow you to handle cases differently based on the user input. In those cases, a custom processor seems more reasonable. We could also consider making the SimplePicture
class swappable.
Cheers!
Joe
from django-pictures.
Related Issues (20)
- How to work with static files? HOT 1
- PNGs (probably ones with alpha-channel) are not processed properly
- Error in documentation? HOT 1
- I/O operation on closed file. HOT 2
- django-storages compatibility HOT 2
- unable to setup HOT 1
- Support for Django-RQ HOT 3
- django-pictures using file JPG type format throws an error 'JPG' HOT 2
- Should `aspect_ratios` default to `None`? HOT 1
- Migration to PictureField fails with blank=True
- Placeholder URL pattern doesn't support alt text containing slashes
- NoReverseMatch : 'pictures' is not a registered namespace HOT 4
- Styling / Class on picture tag HOT 1
- Allow posting Pictures with DRF HOT 1
- HINT: Set both the width_field and height_field attribute to avoid storage IO
- Exception while saving HOT 2
- With Celery only the initial image is being created HOT 4
- Image gets rotated HOT 1
- With dramatiq only the initial picture is being created
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from django-pictures.