Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@homm
Copy link
Member

@homm homm commented Dec 17, 2019

This PR is a logical conclusion of #4251 and #4231 (and also the old one #2254) and shouldn't be merged first. All the new code in the single commit: fa9e85c

With all these tools (.reduce() method and box argument for .resize() method) we finally have an extremely precise image resize approximation which works in times faster on a very large image reduce.

Performance

In [1]: from PIL import Image 
   ...: im = Image.open('../imgs/space.jpeg') 
   ...: im.load() 
   ...: 'Size: {}x{}'.format(*im.size)
Out[1]: 'Size: 4928x3280'

In [2]: %timeit im.resize((411, 273), Image.BICUBIC, reducing_gap=None)
75.6 ms ± 331 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [3]: %timeit im.resize((411, 273), Image.BICUBIC, reducing_gap=5.0)
36.7 ms ± 295 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [4]: %timeit im.resize((411, 273), Image.BICUBIC, reducing_gap=4.0)
42 ms ± 192 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [5]: %timeit im.resize((411, 273), Image.BICUBIC, reducing_gap=3.0)
27.4 ms ± 343 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [6]: %timeit im.resize((411, 273), Image.BICUBIC, reducing_gap=2.0)
18 ms ± 39.1 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

In [7]: %timeit im.resize((411, 273), Image.BICUBIC, reducing_gap=1.0)
11.4 ms ± 127 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

With a very realistic value (reducing_gap=2.0) this method speed ups resizing in 4.2 times. This like Pillow-SIMD but without SIMD :-)

It performs even better with more heavy LANCZOS filter (5.3 times faster):

In [10]: %timeit im.resize((411, 273), Image.LANCZOS, reducing_gap=None)                                                          
109 ms ± 239 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [11]: %timeit im.resize((411, 273), Image.LANCZOS, reducing_gap=5.0)                                                           
48.5 ms ± 253 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [12]: %timeit im.resize((411, 273), Image.LANCZOS, reducing_gap=4.0)                                                           
50 ms ± 238 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [13]: %timeit im.resize((411, 273), Image.LANCZOS, reducing_gap=3.0)                                                           
30.7 ms ± 280 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [14]: %timeit im.resize((411, 273), Image.LANCZOS, reducing_gap=2.0)                                                           
20.3 ms ± 281 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [15]: %timeit im.resize((411, 273), Image.LANCZOS, reducing_gap=1.0)                                                           
12 ms ± 84 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Accuracy

try_reduce.py
from PIL import Image

im = Image.open('../imgs/space.jpeg')
im.load()
print('Size: {}x{}'.format(*im.size))

out_size = (320, 213)

im.resize(out_size, Image.BICUBIC, reducing_gap=None).save('_bic.ref.png')
for reducing_gap in range(1, 4):
    im.resize(out_size, Image.BICUBIC, reducing_gap=reducing_gap).save(f'_bic.{reducing_gap}.png')
    
im.resize(out_size, Image.LANCZOS, reducing_gap=None).save('_lzs.ref.png')
for reducing_gap in range(1, 4):
    im.resize(out_size, Image.LANCZOS, reducing_gap=reducing_gap).save(f'_lzs.{reducing_gap}.png')

Bicubic

Reference (reducing_gap=None):
_bic ref

reducing_gap=3.0 and inverted difference (yes, there is the second image):
_bic 3 _bic 3 diff

reducing_gap=2.0 and inverted difference (wipe your screen):
_bic 2 _bic 2 diff

reducing_gap=1.0 and inverted difference:
_bic 1 _bic 1 diff

Lanczos

Reference (reducing_gap=None):
_lzs ref

reducing_gap=3.0 and inverted difference:
_lzs 3 _lzs 3 diff

reducing_gap=2.0 and inverted difference:
_lzs 2 _lzs 2 diff

reducing_gap=1.0 and inverted difference:
_lzs 1 _lzs 1 diff

So the difference between fair resampling and reducing_gap=2.0 is indistinguishable by eye. reducing_gap more than 3.0 is simply meaningless.

Bonus track: the inverted difference between BICUBIC and LANCZOS:
_bic ref diff

TODO list

  • tests
  • use box argument from reduce() method to reduce image region
  • release notes

src/PIL/Image.py Outdated
return m_im

def resize(self, size, resample=NEAREST, box=None):
def resize(self, size, resample=NEAREST, box=None, max_reduce=None):
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe the better name is reduce_gap since this is actually minimal distance (in terms of scale) between the reduce() method result size and the requested size.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed to reducing_gap

src/PIL/Image.py Outdated
if res is not None:
box = res[1]
if max_reduce is not None:
res = self.draft(None, (size[0] * max_reduce, size[1] * max_reduce))
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the one hand, thumbnail() now takes max_reduce into account when working with JPEGs and achieves indistinguishable quality like when it works with any other images.

src/PIL/Image.py Outdated

if self.size != size:
im = self.resize(size, resample, box=box)
im = self.resize(size, resample, box=box, max_reduce=max_reduce)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the other hand, thumbnail() takes max_reduce into account when working with any other images and achieves compared speed as when working with JPEGs.

Huge win!

@homm homm marked this pull request as ready for review December 20, 2019 17:20
homm added 2 commits December 20, 2019 20:27
# Conflicts:
#	docs/releasenotes/7.0.0.rst
#	src/PIL/Image.py
@homm homm removed the Do Not Merge label Dec 27, 2019
Copy link
Member

@hugovk hugovk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few minor suggestions.

^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Speeds up resizing by resizing the image in two steps. The bigger ``reducing_gap``,
the closer the result to the fair resampling. The smaller ``reducing_gap``,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is meant by "fair resampling"?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This means resampling in one operation, as before, without .reduce() operation.

homm added 2 commits December 30, 2019 03:23
# Conflicts:
#	docs/releasenotes/7.0.0.rst
@homm homm merged commit c3232e5 into python-pillow:master Dec 30, 2019
@homm homm deleted the reduce-in-resize branch December 30, 2019 14:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants