Skip to content
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

Merged
merged 3 commits into from
Apr 16, 2023
Merged

Image attachments improvements #4037

merged 3 commits into from
Apr 16, 2023

Conversation

iequidoo
Copy link
Collaborator

@iequidoo iequidoo commented Feb 14, 2023

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.

@iequidoo
Copy link
Collaborator Author

iequidoo commented Feb 14, 2023

Btw, we can easily remove Exif from all images if it's ok to recode them to JPEG @link2xt
EDIT: Decided to remove only from JPEG for now

@iequidoo iequidoo marked this pull request as ready for review February 14, 2023 17:06
@iequidoo iequidoo requested review from link2xt and r10s February 14, 2023 17:06
@link2xt
Copy link
Collaborator

link2xt commented Feb 14, 2023

Why test images are changed in the commit?

@iequidoo
Copy link
Collaborator Author

Why test images are changed in the commit?

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

@iequidoo iequidoo requested a review from link2xt February 16, 2023 14:03
@iequidoo iequidoo mentioned this pull request Feb 20, 2023
@iequidoo iequidoo force-pushed the iequidoo/no-exif branch 2 times, most recently from a8dca19 to 20a5328 Compare February 21, 2023 22:35
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
Copy link
Collaborator

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.

Copy link
Collaborator Author

@iequidoo iequidoo Feb 24, 2023

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

Copy link
Collaborator

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?

Copy link
Collaborator

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

Copy link
Collaborator Author

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!

@iequidoo iequidoo marked this pull request as draft February 23, 2023 13:48
@iequidoo iequidoo changed the title Remove metadata from avatars and JPEG images before sending (#4027) Image attachments improvements Feb 24, 2023
@iequidoo iequidoo marked this pull request as ready for review February 24, 2023 14:32
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();
Copy link
Collaborator

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.

Copy link
Collaborator Author

@iequidoo iequidoo Mar 1, 2023

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

Copy link
Collaborator

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

Copy link
Collaborator Author

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

Copy link
Collaborator Author

@iequidoo iequidoo Mar 6, 2023

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

Copy link
Collaborator Author

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

Copy link
Collaborator Author

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?

Copy link
Collaborator Author

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.

Copy link
Collaborator Author

@iequidoo iequidoo Mar 6, 2023

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.

@iequidoo iequidoo requested a review from link2xt March 2, 2023 11:45
Copy link
Collaborator

@adbenitez adbenitez left a 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! 🆒

@iequidoo iequidoo marked this pull request as draft March 6, 2023 04:42
@r10s
Copy link
Contributor

r10s commented Mar 7, 2023

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:

(1.8 MB)
Screenshot 2023-03-07 at 22 02 49

here is the same as JPG (as the previous state of the pr was) - some artefacts, but much better:

(0.5 MB)
Screenshot 2023-03-07 at 22 03 01

Copy link
Contributor

@r10s r10s left a 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.

@iequidoo
Copy link
Collaborator Author

@r10s what's is the correct workflow here to tell github that the requested changes are done?

@missytake
Copy link
Contributor

@r10s what's is the correct workflow here to tell github that the requested changes are done?

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.

@iequidoo
Copy link
Collaborator Author

iequidoo commented Apr 3, 2023

@r10s, can we merge this now as 1.36 is out?

@gerryfrancis
Copy link
Contributor

@iequidoo I think you have to rerequest a review from @r10s .

@iequidoo iequidoo requested review from r10s and removed request for r10s April 5, 2023 21:58
@r10s
Copy link
Contributor

r10s commented Apr 6, 2023

sorry, i did not manage yet to dive into this issue again. but i also do not think that i am the bottleneck here?

as the general problems (here and here) seems to be targeted, i removed my "changes requested" (or i tried :) someone needs to look closer to the code again, probably

@r10s r10s self-requested a review April 6, 2023 11:35
@iequidoo
Copy link
Collaborator Author

@link2xt, i think the final decision for merging this is yours :)

@Simon-Laux
Copy link
Contributor

but then a user always can send an image as a file.

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)) {
Copy link
Collaborator

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),
Copy link
Collaborator

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()
Copy link
Collaborator

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?

Copy link
Collaborator Author

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() {
Copy link
Collaborator

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?

Copy link
Collaborator Author

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

@r10s
Copy link
Contributor

r10s commented Apr 15, 2023

but then a user always can send an image as a file.

not really convinient/usable as normal user on iOS yet: deltachat/deltachat-ios#1840

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 :)

also this only affects images, not stickers right?

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.
Copy link
Contributor

@r10s r10s left a 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!

@iequidoo iequidoo merged commit 4dfe34e into master Apr 16, 2023
@iequidoo iequidoo deleted the iequidoo/no-exif branch April 16, 2023 19:32
@r10s
Copy link
Contributor

r10s commented Apr 16, 2023

\o/ thanks for merging. i think, this is a really great improvement! looking forward to test that out in practise

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants