-
Notifications
You must be signed in to change notification settings - Fork 96
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Image attachments improvements #4037
Conversation
681c1c4
to
dbfa144
Compare
Btw, we can easily remove Exif from all images if it's ok to recode them to JPEG @link2xt |
Why test images are changed in the commit? |
dbfa144
to
12dceca
Compare
Yes, only avatar1000x1000.jpg needs to be changed, added some Exif there -- there was no one. So, the tests check that there's an Exif in a source image and no Exif in a received one |
12dceca
to
7f3e6e5
Compare
a8dca19
to
20a5328
Compare
CHANGELOG.md
Outdated
@@ -10,6 +10,8 @@ | |||
- Use transaction in `update_blocked_mailinglist_contacts`. #4058 | |||
- Remove `Sql.get_conn()` interface in favor of `.call()` and `.transaction()`. #4055 | |||
- Updated provider database. | |||
- Remove metadata from avatars and images before sending #4037 | |||
- Reduce and recode images to JPEG if they exceed limits on width or height #4037 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This second change is not as simple, I suggest moving it to another PR for now and doing separately.
JPEG does not support transparency, so if the PNG image is some sort of sticker, it will get a solid color background when converted to JPEG.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe, but let's give it another try ^)
And as for transparancy, in #3956 afaiu we agreed with @r10s that sacrificing transparency is better than sending huge images. Anyway a user can send an image as a file. And also most of PNGs (incl. screenshots) won't be recoded because 500K looks quite enough for them
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
images with viewtype sticker should't be recoded IMHO, they can be animated webp or APNG, but then they also have small width and height so probably don't get affected by this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah, it got changed to >500k instead of width and height, I think an animated webp or APNG (even bigger sizes than webp) could be even 1MB I don't think it is good to mess with stickers, and you can't send them as files
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For stickers we use Viewtype::Sticker
and for GIFs ::Gif
. So, they aren't affected, this code is for Viewtype::Image
and avatars. But as for animated webp and APNG, u're right, i need to fix it. Thanks!
20a5328
to
eb508c0
Compare
src/blob.rs
Outdated
== Some((Viewtype::Image, "image/jpeg")); | ||
// We need to rewrite JPEGs with Exif to remove metadata such as location, | ||
// camera model, etc. | ||
let rm_exif = is_jpeg && exif.is_some(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why check is_jpeg
here? If we managed to extract exif from png, we also want to rewrite it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I checked that on Android and in KDE there is no Exif in PNG screenshots. And we discussed with @r10s in #3956 that currently we can leave Exif in PNGs and other non-JPEGs while there's no evidence that they can carry a sensetive info. But i think removing it is also ok (because, again, screenshots likely have no Exif).
To be honest, i'd rather remove it. Let's vote
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I vote for also removing from PNG if it is cheap/easy to do as for jpeg and if it won't affect the PNG image
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The only problem is that now we're not able to save PNGs, we maybe need some crate for this. On the other hand, removing Exif is just removing some bytes from the container. Will try to do
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As discussed with @link2xt in #4027 (comment) , let's recode images to JPEG to remove Exif for now. Most of PNGs, incl. screenshots, don't contain Exif. As for animated images, IDK what to do with them, i tried to save animated PNGs as PNGs, but the image
crate we use don't preserve animation. Maybe we should send them in original size for now, the only question is how to guess if an image is animated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I even think that for all animated images ViewType::Gif
must be used (maybe rename it) so that this code won't run for them
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, we can use the image
crate to detect animated images, but this needs separate handling of PNGs and WebPs, there is nothing like is_animated()
function, we have to decode an image using image::codecs
stuff and check then. Ugly. And if it's animated, handle it as ViewType::Gif
(renamed to e.g. ViewType::Animation
). Should be done in a separate commit of course. @adbenitez, @link2xt, do u have better ideas?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is also image_utils
crate having Info::frames: u32
thing that is > 1 for animated images, but it doesn't support PNGs and WebPs, only Gifs :) And also requires decoding first.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another option is to go simple for now and recode all images with Exif or big ones to JPEG. A user still can send big animated images as files.
So, big animated images will be sent as JPEGs with their first frame. I think this won't happen usually because animated images are often small, i.e. < 500K. Screenshots are big, but they aren't animated. But if a user set MediaQuality::Worse
, they may have problems, 130K as in the PR may be not enough for animated images.
UPD: Looks like i'm too optimistic. APNGs rather need 1M, 500K is not sufficient.
UPD: Also there's an option to increase the weight limit for PNGs and WebPs to 1M.
UPD: Finally i think that 750K is sufficient, this one -- https://upload.wikimedia.org/wikipedia/commons/7/7e/Brain_MRI_apng_105px_100ms.png -- is big enough and it's not easy to find bigger sticker-sized animated PNGs or WebPs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
looking forward to use this! great improvement! you are my personal hero! for real! 🆒
eb508c0
to
6fefbc0
Compare
the current state of the pr does not recode PNG -> JPG any longer but does scale down PNG and leave it as PNG. this wastes much space: eg. the sample screenshot of of #3956 (comment) is 1.8 MB when scaled down - while only 0.5 MB as JPG/90% (70% even less and still fine). but also, it looks worse, the scaling of the image crate adds quite some noise (look at the bolder font and the even bolder "t" of "locally generated") and overall pretty pixelated: here is the same as JPG (as the previous state of the pr was) - some artefacts, but much better: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i suggest to revert the last changes and to stay with recoding PNG -> JPG.
there are very few disadvantages with that, see discussion above, that outweight the advantages by far.
for transparency and animation, i would for now do no "extra things" - stickers are not affected, small PNG are not recoded, animations usually have a different extension - so, there are few cases with issues only. detection also seems to be hard - but we can still iterate over that in a subsequent pr, but i would wait if that turns out to be really needed.
@r10s what's is the correct workflow here to tell github that the requested changes are done? |
e445357
to
039a21f
Compare
by re-requesting a review I think :) which you already did. If there is no reaction after some time it can make sense to ask a reviewer per DM about the state of the review/PR. In this case I'd ask r10s after the 1.36 releases are out. |
@r10s, can we merge this now as 1.36 is out? |
039a21f
to
d891640
Compare
@link2xt, i think the final decision for merging this is yours :) |
not really convinient/usable as normal user on iOS yet: deltachat/deltachat-ios#1840 also this only affects images, not stickers right? |
src/blob.rs
Outdated
let mut encoded = Vec::new(); | ||
let mut changed_name = None; | ||
|
||
let exceeds_width = img.width() > img_wh || img.height() > img_wh; | ||
if matches!(orientation, Some(90) | Some(180) | Some(270)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This if
seems unnecessary, match
inside checks the same already.
src/blob.rs
Outdated
let fmt = ImageFormat::from_path(&blob_abs); | ||
let ofmt = match fmt { | ||
Ok(ImageFormat::Png) if !exceeds_max_bytes => ImageOutputFormat::Png, | ||
_ => ImageOutputFormat::Jpeg(75), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is not clear what 75 means, had to look up the documentation. I would add a let jpeg_quality = 75;
line.
let do_scale = exceeds_max_bytes | ||
|| strict_limits | ||
&& (exceeds_wh | ||
|| exif.is_some() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't get this condition, why exif.is_some()
is only checked if strict_limts = true
and encoded_img_exceeds_bytes(...) = true
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This expr is only for whether we should scale the image. So, if there's an Exif, we're going to recode the image (currently we can't just remove it using the image
crate) and that's why if strict_limits
is set, we're looking at encoded_img_exceeds_bytes(...)
.
|
||
// The file format is JPEG now, we may have to change the file extension | ||
if !matches!(ImageFormat::from_path(&blob_abs), Ok(ImageFormat::Jpeg)) { | ||
if do_scale || exif.is_some() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why exif.is_some()
is checked here if we only rewrite the image if do_scale
is true?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To remove Exif. So, actually we rewrite the image (recode it) if there's an Exif also
thanks for the pointer, let's continue discussion about that aspect at deltachat/deltachat-ios#1840 , not here. this pr is unrelated and ios should not block things here (it is already complicated enough here :)
this pr? this pr should not affect stickers, see discussion above |
If there's an Exif, rewrite the file to remove it. This implies recoding now though.
I.e. > 500K for the balanced media quality and 130K for the worse one. This can remove animation and transparency from PNG/WebP, but then a user always can send an image as a file. Also don't reduce wide/high images if they aren't huge. Among other benefits, this way most of PNG screenshots aren't touched. Also remove Exif from all images, not from JPEGs only.
d891640
to
dfbe5ea
Compare
dfbe5ea
to
5269673
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
was also playing along, re-testing things i critisized before - much better!
\o/ thanks for merging. i think, this is a really great improvement! looking forward to test that out in practise |
Remove metadata from avatars and JPEG images before sending (#4027)
If there's an Exif, rewrite the file to remove it. This implies recoding now though.
Reduce + recode images to JPEG if they are too huge (#3956)
I.e. > 500K for the balanced media quality and 130K for the worse one. This can remove animation and
transparency from PNG/WebP, but then a user always can send an image as a file.
Also don't reduce wide/high images if they aren't huge. Among other benefits, this way most of PNG
screenshots aren't touched.
Also remove Exif from all images, not from JPEGs only.