Skip to content

Replace decoders with libvips, add tests and rename package#1

Open
Rodrigodd wants to merge 30 commits intomihonapp:mainfrom
Rodrigodd:integrate-libvips
Open

Replace decoders with libvips, add tests and rename package#1
Rodrigodd wants to merge 30 commits intomihonapp:mainfrom
Rodrigodd:integrate-libvips

Conversation

@Rodrigodd
Copy link

@Rodrigodd Rodrigodd commented Dec 1, 2024

This PR integrates libvips to get a better algorithm for downsampling images (mangas frequently use half-tone shading, which is very susceptible to aliasing when downsampling). As a bonus, it will allow moving all image format implementations upstream, decreasing the maintenance burden. Libvips also aims to efficiently partially decoding images, possibly decreasing CPU and memory usage (but this still need be measure).

Early discussions: Rodrigodd#1, tachiyomiorg#15

From my tests, the downsampling filter didn't help much. subsampling-scale-image-view will still draw tiles up to 2x the screen resolution, and halftone patterns in manga are not small enough to be filtered out.

This PR is still missing:

@AntsyLich
Copy link
Member

About the benchmarks (that was in the TODO of Rodrigodd#1) can you also include one for the old decoder. someone was interested in comparison.

@AntsyLich
Copy link
Member

Also change the library/src/(androidTest|main)/java folder to library/src/(androidTest|main)/kotlin

@AntsyLich
Copy link
Member

AntsyLich commented Dec 1, 2024

Also we don't want to allow every arbitrary format so there should be a whitelist (refer to the ImageType.Format enum)

@AntsyLich
Copy link
Member

And for tests you can use https://github.com/ReactiveCircus/android-emulator-runner (there is a lit of production usage at the bottom)

@Rodrigodd
Copy link
Author

I added a benchmark module, but I could not retrieve the profile information. The gradlew task errors out in a adb command timeout, and I could not find the profile files in my phone. The benchmark still need to sample regions in a more suitable distribution, test more image formats, etc. And then rebase it to before making all changes, to compare the perfomance.

I also cherry-pick @wwww-wwww fix for the color profile. But loading the test images he provide me on Mihon makes it crash. I still could not reproduce it in a unit-test and neither Debug Mihon directly. But I don't know if his changes is causing it, I only test the images after the changes.

I don't have much time throughout the week, but I will continue to slowly work on those.

Also only run assembleDebug for now, full assemble is too slow.
Also rely on the existing build tool of each external project, instead
of patching CMake into them.

Most `ExternalProject_Add` wrappers were copied from Komelia, at
[https://github.com/Snd-R/Komelia/tree/8b938397e5c8743d4e995d2cc16203367de6f67d/image-decoder/native/cmake],
but modified to make sure all libraries are staticaly linked.
Took me a while to figure out how the API work
Was trying to optimize by cropping the image to the desired region, and
then resizing only that, but libvips seems to already be smart enough,
so that is not necessary.
The intermediate buffer is not necessary, the `decode` function will
already take care of outputing in the correct format.
Was looping to detect memory corruption. Not needed anymore.
And groupId/artifactId as `dev.mihon:image-decoder`
Rodrigodd and others added 7 commits December 7, 2024 20:27
Only `bounds` were being used, so I only keeped that.
This folder is genrated by clangd
Mihon was crashing somethimes crashing when decoding the first image. I
notice Mihon was decoding two images at the same time, which made me
think it could be a threading issue. Reading libvips documentation, it
states that it is safe, but VIPS_INIT must only be called in a
single-thread. I was calling VIPS_INIT for every decoder created, which
I believe was the issue.

This commit moves VIPS_INIT to JNI_OnLoad, which is called only once
when the library is loaded.
The emulator is failing to create due to lack of space. Maybe the
previous build is using too much? I removed the build step, but it will
still be built after the emulator is created, let's see if it will still
break.
This will just check if the benchmark can run without errors.
I am not very confident that building with the cache will work, even
more if we modify the CMakeLists.txt file between builds. But waiting
>20min per build is very painful (and there are both Debug and Release
builds).
@Rodrigodd
Copy link
Author

Got the tests to run on CI, and I got the benchmarks to work in both the old and new implementation. I could not get a reliable benchmark on my device, but it roughly appears that both implementations have the same performance:

OLD:

Name        | Median    | Min       | Max       | Stddev    | Alloc
decodeAvif  | 10.47 ms  | 5.54 ms   | 13.62 ms  | 2.13 ms   |  10
decodeAvif  | 10.00 ms  | 6.27 ms   | 15.39 ms  | 2.07 ms   |  10
decodeAvif  | 10.48 ms  | 6.65 ms   | 14.90 ms  | 2.32 ms   |  10

decodeHeif  | 10.28 ms  | 5.26 ms   | 13.82 ms  | 2.20 ms   |  10
decodeHeif  | 10.26 ms  | 6.77 ms   | 15.40 ms  | 2.16 ms   |  10
decodeHeif  | 10.71 ms  | 7.54 ms   | 16.04 ms  | 1.57 ms   |  10

decodeWebp  | 15.60 ms  | 10.76 ms  | 23.18 ms  | 3.01 ms   |  10
decodeWebp  | 15.42 ms  | 10.84 ms  | 22.30 ms  | 3.20 ms   |  10
decodeWebp  | 16.01 ms  | 10.42 ms  | 23.02 ms  | 2.97 ms   |  10

decodeJpg   | 15.26 ms  | 10.25 ms  | 21.74 ms  | 2.59 ms   |  10
decodeJpg   | 14.77 ms  | 10.32 ms  | 22.51 ms  | 2.70 ms   |  10
decodeJpg   | 15.81 ms  | 10.58 ms  | 23.93 ms  | 3.08 ms   |  10

decodeJxl   | 14.76 ms  | 10.20 ms  | 21.78 ms  | 2.10 ms   |  10
decodeJxl   | 15.72 ms  | 10.34 ms  | 22.54 ms  | 3.00 ms   |  10
decodeJxl   | 15.54 ms  | 10.67 ms  | 22.72 ms  | 3.30 ms   |  10

decodePng   | 15.12 ms  | 11.53 ms  | 20.77 ms  | 2.01 ms   |  10
decodePng   | 15.67 ms  | 10.41 ms  | 21.93 ms  | 3.04 ms   |  10
decodePng   | 14.97 ms  | 10.81 ms  | 22.23 ms  | 2.58 ms   |  10

VIPS:

Name        | Median    | Min       | Max       | Stddev    | Alloc
decodeAvif  | 10.01 ms  | 5.50 ms   | 14.37 ms  | 2.22 ms   |  10
decodeAvif  | 10.82 ms  | 6.67 ms   | 17.02 ms  | 1.95 ms   |  10
decodeAvif  | 10.45 ms  | 6.97 ms   | 14.97 ms  | 1.94 ms   |  10

decodeHeif  | 10.63 ms  | 8.01 ms   | 14.63 ms  | 1.58 ms   |  10
decodeHeif  | 10.54 ms  | 6.94 ms   | 14.55 ms  | 1.86 ms   |  10
decodeHeif  | 10.91 ms  | 7.16 ms   | 16.45 ms  | 1.97 ms   |  10

decodeWebp  | 15.16 ms  | 11.34 ms  | 20.35 ms  | 1.81 ms   |  10
decodeWebp  | 15.79 ms  | 10.42 ms  | 22.92 ms  | 2.97 ms   |  10
decodeWebp  | 15.62 ms  | 11.18 ms  | 23.88 ms  | 3.34 ms   |  10

decodeJpg   | 15.50 ms  | 10.34 ms  | 21.96 ms  | 2.99 ms   |  10
decodeJpg   | 15.84 ms  | 10.73 ms  | 21.13 ms  | 2.54 ms   |  10
decodeJpg   | 15.24 ms  | 11.35 ms  | 21.44 ms  | 1.79 ms   |  10

decodeJxl   | 14.67 ms  | 10.58 ms  | 21.21 ms  | 2.51 ms   |  10
decodeJxl   | 14.68 ms  | 10.57 ms  | 21.34 ms  | 2.60 ms   |  10
decodeJxl   | 15.66 ms  | 10.32 ms  | 22.69 ms  | 3.08 ms   |  10

decodePng   | 15.55 ms  | 10.70 ms  | 21.56 ms  | 2.62 ms   |  10
decodePng   | 15.10 ms  | 11.43 ms  | 21.22 ms  | 1.72 ms   |  10
decodePng   | 14.86 ms  | 10.51 ms  | 21.59 ms  | 2.59 ms   |  10

I think the only thing missing now is the resizing in linear color space. From some tests, I see that getting it to work would have a performance cost of 2.5x to 5x to 10x, depending on how it is implemented or measured. Here is the benchmark I used, but I only measure the performance, I didn't evaluated if the results are any good (I also do not yet know how to do that), so the implementations in that benchmark may be flawed.

@AntsyLich, @wwww-wwww it is important to get linear color space resizing before merging this? I think it is important for manga, due to the heavy use of half-tone shading. But the previous implementation had that? That is, would merging this bring a regression?

@AntsyLich
Copy link
Member

Did you mean the previous implementation had color space resizing?

@Rodrigodd
Copy link
Author

Yeah, I am asking if the previous implementation already does the resizing in a linear light color space, or it is just a new feature we want.

@octopushugger
Copy link

@Rodrigodd I don't know what the previous implementation does but I'd argue it is important. Downscaling half-tones in linear light can significantly reduce moire patterns.

Here is a comparison (click on the image to switch back and forth): https://slow.pics/c/D3POpbEy

As for upscaling I've seen people say linear light is worse for it as it can cause ringing and it should be disabled or done in sigmoidal color. In a quick test of mine I couldn't really tell the difference.

@riki270601
Copy link

Is there any chance this feature will be implemented?

@Rodrigodd
Copy link
Author

Some time ago I tried to implement linear light resampling with libvips, but I could not discover how to do it. If I remember correctly there is some problems with ICC color management which a clear solution would be better implemented upstream, in libvips.

Currently I don't have time to work on this project, so it will be a while before there will be any progress here from my part. I also lose a bit of my motivation when I realize this will not really fix mihonapp/mihon#42 by itself (for down-scaling, at least), which was my initial goal.

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.

5 participants