Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package imgp for openSUSE:Factory checked in 
at 2026-08-25 13:20:46
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/imgp (Old)
 and      /work/SRC/openSUSE:Factory/.imgp.new.1258 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "imgp"

Tue Aug 25 13:20:46 2026 rev:9 rq:1373540 version:3.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/imgp/imgp.changes        2026-08-05 
17:49:17.227700394 +0200
+++ /work/SRC/openSUSE:Factory/.imgp.new.1258/imgp.changes      2026-08-25 
13:21:39.245270221 +0200
@@ -1,0 +2,12 @@
+Tue Aug 25 07:38:16 UTC 2026 - Andrea Manzini <[email protected]>
+
+- Update to version 3.0
+  * Add megapixel target functionality
+  * Add an option to raise the PIL/Pillow pixel limit for trusted input files
+  * Fix incorrect gamma handling during resizing
+  * Add Python 3.12, 3.13 and 3.14 support
+  * Retire Python 3.8 and 3.9 support (EOL)
+  * Add -v and --version options
+  * Add version option completion for Bash, Fish, and Zsh
+
+-------------------------------------------------------------------

Old:
----
  imgp-2.9.tar.gz

New:
----
  imgp-3.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ imgp.spec ++++++
--- /var/tmp/diff_new_pack.MSWWPk/_old  2026-08-25 13:21:40.287306966 +0200
+++ /var/tmp/diff_new_pack.MSWWPk/_new  2026-08-25 13:21:40.289307037 +0200
@@ -22,7 +22,7 @@
 %define _fish_completions %{_datadir}/fish/vendor_completions.d
 %endif
 Name:           imgp
-Version:        2.9
+Version:        3.0
 Release:        0
 Summary:        Image resizer and rotator
 License:        GPL-3.0-or-later

++++++ imgp-2.9.tar.gz -> imgp-3.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/imgp-2.9/.circleci/config.yml 
new/imgp-3.0/.circleci/config.yml
--- old/imgp-2.9/.circleci/config.yml   2023-09-10 05:42:25.000000000 +0200
+++ new/imgp-3.0/.circleci/config.yml   2026-08-16 08:52:22.000000000 +0200
@@ -1,4 +1,4 @@
-version: 2
+version: 2.1
 
 test-template: &test-template
   working_directory: ~/imgp
@@ -17,24 +17,29 @@
           make test
 
 jobs:
-  py38:
+  py310:
     docker:
-      - image: python:3.8-slim
+      - image: python:3.10-slim
     <<: *test-template
 
-  py39:
+  py311:
     docker:
-      - image: python:3.9-slim
+      - image: python:3.11-slim
     <<: *test-template
 
-  py310:
+  py312:
     docker:
-      - image: python:3.10-slim
+      - image: python:3.12-slim
     <<: *test-template
 
-  py311:
+  py313:
     docker:
-      - image: python:3.11-slim
+      - image: python:3.13-slim
+    <<: *test-template
+
+  py314:
+    docker:
+      - image: python:3.14-slim
     <<: *test-template
 
 #  package-and-publish:
@@ -56,14 +61,14 @@
 #            ghr -t ${GITHUB_API_TOKEN} -u ${CIRCLE_PROJECT_USERNAME} -r 
${CIRCLE_PROJECT_REPONAME} -c ${CIRCLE_SHA1} -replace ${CIRCLE_TAG} ./dist/
 
 workflows:
-  version: 2
-
   CircleCI:
     jobs: &all-tests
-      - py38
-      - py39
       - py310
       - py311
+      - py312
+      - py313
+      - py314
+
 
   nightly:
     triggers:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/imgp-2.9/.github/workflows/lock.yml 
new/imgp-3.0/.github/workflows/lock.yml
--- old/imgp-2.9/.github/workflows/lock.yml     2023-09-10 05:42:25.000000000 
+0200
+++ new/imgp-3.0/.github/workflows/lock.yml     1970-01-01 01:00:00.000000000 
+0100
@@ -1,17 +0,0 @@
-name: 'Lock threads'
-
-on:
-  schedule:
-    - cron: '0 0 * * *'
-
-jobs:
-  lock:
-    runs-on: ubuntu-latest
-    steps:
-      - uses: dessant/lock-threads@v2
-        with:
-          github-token: ${{ github.token }}
-          issue-lock-inactive-days: '30'
-          issue-lock-reason: ''
-          pr-lock-inactive-days: '30'
-          pr-lock-reason: ''
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/imgp-2.9/CHANGELOG new/imgp-3.0/CHANGELOG
--- old/imgp-2.9/CHANGELOG      2023-09-10 05:42:25.000000000 +0200
+++ new/imgp-3.0/CHANGELOG      2026-08-16 08:52:22.000000000 +0200
@@ -1,3 +1,16 @@
+imgp v3.0
+2026-08-16
+
+- Add megapixel target functionality
+- Add an option to raise the PIL/Pillow pixel limit for trusted input files
+- Fix incorrect gamma handling during resizing
+- Add Python 3.12 support
+- Retire Python 3.8 and 3.9 support (EOL)
+- Add `-v` and `--version` options
+- Add version option completion for Bash, Fish, and Zsh
+
+-------------------------------------------------------------------------------
+
 imgp v2.9
 2023-09-10
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/imgp-2.9/README.md new/imgp-3.0/README.md
--- old/imgp-2.9/README.md      2023-09-10 05:42:25.000000000 +0200
+++ new/imgp-3.0/README.md      2026-08-16 08:52:22.000000000 +0200
@@ -2,14 +2,14 @@
 
 <p align="center">
 <a href="https://github.com/jarun/imgp/releases/latest";><img 
src="https://img.shields.io/github/release/jarun/imgp.svg?maxAge=600"; 
alt="Latest release" /></a>
-<a href="https://repology.org/project/imgp/versions";><img 
src="https://repology.org/badge/tiny-repos/imgp.svg"; alt="Availability"></a>
+<a href="https://repology.org/project/imgp/versions";><img 
src="https://repology.org/badge/tiny-repos/imgp.svg?header=repos"; 
alt="Availability"></a>
 <a href="https://pypi.org/project/imgp/";><img 
src="https://img.shields.io/pypi/v/imgp.svg?maxAge=600"; alt="PyPI" /></a>
 <a href="https://circleci.com/gh/jarun/workflows/imgp";><img 
src="https://img.shields.io/circleci/project/github/jarun/imgp.svg"; alt="Build 
Status" /></a>
-<a href="https://github.com/jarun/imgp/blob/master/LICENSE";><img 
src="https://img.shields.io/badge/license-GPLv3-yellowgreen.svg?maxAge=2592000"; 
alt="License" /></a>
+<a href="https://github.com/jarun/imgp/blob/master/LICENSE";><img 
src="https://img.shields.io/badge/license-GPLv3+-yellowgreen.svg?maxAge=2592000";
 alt="License" /></a>
 </p>
 
 <p align="center">
-<a href="https://asciinema.org/a/88448";><img 
src="https://asciinema.org/a/88448.svg"; alt="imgp_asciicast" width="600"/></a>
+<a href="https://asciinema.org/a/88448";><img 
src="https://github.com/user-attachments/assets/b7efb760-b5f8-4118-beee-69ce9efd8de4";
 alt="imgp_asciicast" width="600"/></a>
 </p>
 
 <p align="center"><i>Watch imgp resize a directory of images in lightning 
speed!</i></p>
@@ -36,11 +36,12 @@
   - [cmdline options](#cmdline-options)
   - [Operational notes](#operational-notes)
 - [Examples](#examples)
+- [Troubleshooting](#troubleshooting)
 - [Developers](#developers)
 
 ### Features
 
-- resize by percentage or resolution
+- resize by percentage or resolution or megapixel target
 - rotate clockwise by specified angle
 - adaptive resize considering orientation
 - brute force to a resolution
@@ -75,15 +76,15 @@
 
 #### Dependencies
 
-`imgp` requires Python 3.8 or later.
+`imgp` requires Python 3.10 or later.
 
 To install PIL library on Ubuntu, run:
 
-    $ sudo apt-get install python3-pil
+    sudo apt-get install python3-pil
 
 or, using pip3:
 
-    $ sudo pip3 install pillow
+    sudo pip3 install pillow
 
 pillow can be replaced by 
[pillow-simd](https://github.com/uploadcare/pillow-simd) on 
[SIMD](https://en.wikipedia.org/wiki/SIMD) processors.
 
@@ -115,11 +116,11 @@
 
 Install to default location (`/usr/local`):
 
-    $ sudo make install
+    sudo make install
 
 To remove, run:
 
-    $ sudo make uninstall
+    sudo make uninstall
 
 `PREFIX` is supported, in case you want to install to a different location.
 
@@ -127,7 +128,7 @@
 
 `imgp` is a standalone utility. From the containing directory, run:
 
-    $ ./imgp
+    ./imgp
 
 ### Shell completion
 
@@ -139,7 +140,7 @@
 
 ```
 usage: imgp [-h] [-x res] [-o deg] [-a] [-c] [-e] [-f] [-H] [-i] [-k] [-m] [-M 
res]
-            [-n] [-N] [-O] [-P] [-q N] [-r] [-s byte] [-w] [-d] [PATH [PATH 
...]]
+            [-n] [-N] [-O] [-P] [-q N] [-r] [-s byte] [-w] [-d] [-v] [PATH ...]
 
 Resize, rotate JPEG and PNG images.
 
@@ -148,7 +149,7 @@
 
 optional arguments:
   -h, --help            show this help message and exit
-  -x res, --res res     output resolution in HxV or percentage
+  -x res, --res res     output resolution in HxV or percentage ("%" suffix) or 
megapixel ("MP" suffix)
   -o deg, --rotate deg  rotate clockwise by angle (in degrees)
   -a, --adapt           adapt to resolution by orientation [default: off]
   -c, --convert         convert PNG to JPG format [default: off]
@@ -169,6 +170,7 @@
   -s byte, --size byte  minimum size to process an image [default: 1024]
   -w, --overwrite       overwrite source images [default: off]
   -d, --debug           enable debug logs [default: off]
+  -v, --version         show program's version number and exit
 ```
 
 #### Operational notes
@@ -208,16 +210,23 @@
        1366x767 -> 1025x575
        120968 bytes -> 45040 bytes
 
-3. Rotate an image clockwise by 90 degrees:
+3. Ensure all images in folder `testphotos/` (contains 4 images) are 12MP at 
maximum:
 
-       $ imgp -o 90  ~/image.jpg
+       $ imgp --res 12MP -w testphotos/
+       testphotos/20250111_193949_ILCE-7M3.jpg
+       6000x4000 -> 4243x2828
+       8355840 bytes -> 3545149 bytes
+
+4. Rotate an image clockwise by 90 degrees:
+
+       $ imgp -o 90 ~/image.jpg
        120968 bytes -> 72038 bytes
 
-4. Adapt the images in the current directory to 1366x1000 resolution. Visit 
all directories recursively, overwrite source images, ignore images with 
matching hres or vres but convert PNG images to JPEG.
+5. Adapt the images in the current directory to 1366x1000 resolution. Visit 
all directories recursively, overwrite source images, ignore images with 
matching hres or vres but convert PNG images to JPEG.
 
        $ imgp -x 1366x1000 -wrack
 
-5. Set hres=800 and adapt vres maintaining the ratio.
+6. Set hres=800 and adapt vres maintaining the ratio.
 
        $ imgp -x 800x0
        Source omitted. Processing current directory...
@@ -230,11 +239,11 @@
        1050x1400 -> 800x1067
        458092 bytes -> 78089 bytes
 
-6. Process images greater than 50KiB only:
+7. Process images greater than 50KiB only:
 
        $ imgp -wrackx 1366x1000 -s 51200
 
-7. Generate a 64px adapative thumbnail of the last modified file in the 
current dir:
+8. Generate a 64px adaptive thumbnail of the last modified file in the current 
dir:
 
        #!/usr/bin/env sh
 
@@ -244,7 +253,13 @@
            imgp -acx 64x64 "$pop"
        }
 
+### Troubleshooting
+
+1. To lift the pixel limit (e.g. error `'PIL.Image.DecompressionBombError' 
when resizing large image`) for _trusted_ input files, uncomment the following 
line in the source code:
+
+       # Image.MAX_IMAGE_PIXELS = None
+
 ### Developers
 
-1. Copyright © 2016-2023 [Arun Prakash Jana](https://github.com/jarun)
+1. Copyright © 2016-2026 [Arun Prakash Jana](https://github.com/jarun)
 2. [Ananya Jana](https://github.com/ananyajana)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/imgp-2.9/auto-completion/bash/imgp-completion.bash 
new/imgp-3.0/auto-completion/bash/imgp-completion.bash
--- old/imgp-2.9/auto-completion/bash/imgp-completion.bash      2023-09-10 
05:42:25.000000000 +0200
+++ new/imgp-3.0/auto-completion/bash/imgp-completion.bash      2026-08-16 
08:52:22.000000000 +0200
@@ -32,6 +32,7 @@
         -x --res
         -d --debug
         -h --help
+        -v --version
     )
     opts_with_arg=(
         -M --minres
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/imgp-2.9/auto-completion/fish/imgp.fish 
new/imgp-3.0/auto-completion/fish/imgp.fish
--- old/imgp-2.9/auto-completion/fish/imgp.fish 2023-09-10 05:42:25.000000000 
+0200
+++ new/imgp-3.0/auto-completion/fish/imgp.fish 2026-08-16 08:52:22.000000000 
+0200
@@ -22,6 +22,7 @@
 complete -c imgp -s r  -l recurse     --description 'process directories 
recursively'
 complete -c imgp -s s  -l size     -r --description 'min byte size to process 
an image'
 complete -c imgp -s w  -l overwrite   --description 'overwrite source images'
-complete -c imgp -s x  -l res      -r --description 'output resolution in HxV 
or %'
+complete -c imgp -s x  -l res      -r --description 'output resolution in HxV 
or % or MP'
 complete -c imgp -s d  -l debug       --description 'enable debug logs'
 complete -c imgp -s h  -l help        --description 'show help'
+complete -c imgp -s v  -l version     --description 'show program version'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/imgp-2.9/auto-completion/zsh/_imgp 
new/imgp-3.0/auto-completion/zsh/_imgp
--- old/imgp-2.9/auto-completion/zsh/_imgp      2023-09-10 05:42:25.000000000 
+0200
+++ new/imgp-3.0/auto-completion/zsh/_imgp      2026-08-16 08:52:22.000000000 
+0200
@@ -27,8 +27,9 @@
     '(-r --recurse)'{-r,--recurse}'[process directories recursively]'
     '(-s --size)'{-s,--size}'[min byte size to process an image]:bytes'
     '(-w --overwrite)'{-w,--overwrite}'[overwrite source images]'
-    '(-x --res)'{-x,--res}'[output resolution]:HxV or %'
+    '(-x --res)'{-x,--res}'[output resolution]:HxV or % or MP'
     '(-d --debug)'{-z,--debug}'[enable debug logs]'
     '(-h --help)'{-h,--help}'[show help]'
+    '(-v --version)'{-v,--version}'[show program version]'
 )
 _arguments -S -s $args
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/imgp-2.9/imgp new/imgp-3.0/imgp
--- old/imgp-2.9/imgp   2023-09-10 05:42:25.000000000 +0200
+++ new/imgp-3.0/imgp   2026-08-16 08:52:22.000000000 +0200
@@ -2,7 +2,7 @@
 #
 # Resize or rotate JPEG and PNG images.
 #
-# Copyright © 2016-2023 Arun Prakash Jana <[email protected]>
+# Copyright © 2016-2026 Arun Prakash Jana <[email protected]>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -23,15 +23,20 @@
 import os
 import sys
 import time
+import math
 import PIL
 from PIL import Image, ImageFile
 
+# Uncomment the below line to lift pixel limit for _trusted_ input files
+# Image.MAX_IMAGE_PIXELS = None
+
 if not hasattr(PIL.Image, 'Resampling'):  # Pillow<9.0
     PIL.Image.Resampling = PIL.Image
 
 # Globals
 HRES = 0  # specified horizontal resolution
 VRES = 0  # specified vertical resolution
+megapixel = None  # specified megapixel target
 scale = None  # specified scale in percentage
 rotate = None  # specified rotation in degree
 adapt = False  # use adaptive resolution
@@ -56,8 +61,7 @@
 pad = '_IMGP'  # output file suffix when --overwrite is unused
 png_ip = Image.Resampling.LANCZOS  # default interpolation for PNG
 fill_color = '#ffffff'  # BG color to strip alpha channel
-init_time = time.time()  # profile the total time taken
-_VERSION_ = '2.9'  # current program version
+_VERSION_ = '3.0'  # current program version
 
 # Globals for multiprocessing
 pool = None
@@ -71,22 +75,79 @@
 
     :param res: resolution in hresxvres format
     '''
-
-    hxv = res.split('x')
-    if len(hxv) != 2:
-        return (0, 0)
-
     try:
-        return (int(hxv[0]), int(hxv[1]))
+        hxv = res.split('x')
+        assert len(hxv) == 2
+        return map(int, hxv)
     except Exception:
         return (0, 0)
 
 
+def gamma_aware_resize(img, isize, resample):
+    '''Resize image with gamma correction for better quality
+
+    Converts to linear color space before resizing, then converts back.
+    This prevents the common error of resizing in gamma-corrected space.
+
+    Fixes issue #60: https://github.com/jarun/imgp/issues/60
+    See also: http://www.ericbrasseur.org/gamma.html
+
+    :param img: PIL Image object
+    :param isize: tuple of (width, height) for target size
+    :param resample: resampling filter to use
+    :return: resized PIL Image object
+    '''
+    # For images without color (e.g., 'L', '1'), no gamma correction needed
+    if img.mode in ('L', '1', 'P'):
+        return img.resize(isize, resample)
+
+    # Handle alpha channel separately if present
+    has_alpha = img.mode in ('RGBA', 'LA')
+    if has_alpha:
+        # Split into color and alpha
+        if img.mode == 'RGBA':
+            r, g, b, a = img.split()
+            rgb_img = Image.merge('RGB', (r, g, b))
+        else:  # LA
+            l, a = img.split()
+            rgb_img = Image.merge('RGB', (l, l, l))
+
+        # Resize alpha separately (no gamma correction needed for alpha)
+        resized_alpha = a.resize(isize, resample)
+    else:
+        rgb_img = img.convert('RGB') if img.mode != 'RGB' else img
+
+    # Apply gamma correction: convert from sRGB to linear
+    # Using simplified sRGB gamma of 2.2 (actual sRGB is more complex but 2.2 
is close)
+    gamma = 2.2
+    linear_img = rgb_img.point(lambda x: pow(x / 255.0, gamma) * 255.0)
+
+    # Resize in linear space
+    resized_linear = linear_img.resize(isize, resample)
+
+    # Convert back to sRGB: apply inverse gamma
+    inv_gamma = 1.0 / gamma
+    resized_rgb = resized_linear.point(lambda x: pow(x / 255.0, inv_gamma) * 
255.0)
+
+    # Reattach alpha if it was present
+    if has_alpha:
+        if img.mode == 'RGBA':
+            r, g, b = resized_rgb.split()
+            result = Image.merge('RGBA', (r, g, b, resized_alpha))
+        else:  # LA - 8-bit pixels, grayscale with alpha
+            result = Image.merge('LA', (resized_rgb.split()[0], resized_alpha))
+        return result
+
+    if img.mode != 'RGB':
+        # Convert back to original mode if needed
+        return resized_rgb.convert(img.mode)
+
+    return resized_rgb
+
+
 def lock_print(msg):
     '''Acquire lock and print a message'''
 
-    global lock
-
     with lock:
         print(msg)
 
@@ -102,8 +163,6 @@
 def traverse_dir(path):
     '''Traverse a directory recursively'''
 
-    global lock, pool
-
     try:
         for entry in os.scandir(path):
             # Add check for hidden here
@@ -123,15 +182,12 @@
                 else:
                     pool.apply_async(resize_image, args=(entry.path,), 
callback=cb)
     except OSError as e:
-        with lock:
-            print(e)
+        lock_print(e)
 
 
 def rotate_image(src):
     '''Rotate a source image'''
 
-    global lock
-
     converted = False
     _progressive = progressive
     name, ext = os.path.splitext(src)
@@ -164,8 +220,7 @@
             exifdata = b''
     except OSError as e:
         if not str(e).startswith('cannot identify image file'):
-            with lock:
-                print(e)
+            lock_print(e)
         return
 
     # Image.rotate(angle, resample=0, expand=0) expand = True means
@@ -182,7 +237,7 @@
 
     if convert and _format == 'PNG':
         if img.mode in ('RGBA', 'LA'):
-            background = Image.new(img.mode[:-1], img.size, fill_color)
+            background = Image.new(img.mode.rstrip('A'), img.size, fill_color)
             background.paste(img, img.split()[-1])
             img = background
         elif img.mode == "P":
@@ -204,15 +259,12 @@
 
         return [1, 0]
     except OSError as e:
-        with lock:
-            print(e)
+        lock_print(e)
 
 
 def resize_image(src):
     '''Resize a source image'''
 
-    global HRES, VRES, minH, minV, lock
-
     converted = False
     _progressive = progressive
     name, ext = os.path.splitext(src)
@@ -257,12 +309,10 @@
             exifdata = b''
     except OSError as e:
         if not str(e).startswith('cannot identify image file'):
-            with lock:
-                print(e)
+            lock_print(e)
         return
 
-    hres = img.size[0]
-    vres = img.size[1]
+    hres, vres = img.size
     ImageFile.MAXBLOCK = hres * vres
 
     antialias = png_ip if _format == 'PNG' else Image.Resampling.LANCZOS
@@ -278,6 +328,24 @@
 
         if convert and _format != 'PNG':
             return
+
+    elif megapixel:
+        img_mp = hres * vres / 1e6
+
+        shrink_factor = math.sqrt(megapixel / img_mp)
+        if shrink_factor >= 1 and not enlarge:
+            return
+
+        _hres = round(hres * shrink_factor)
+        _vres = round(vres * shrink_factor)
+
+        if debug:
+            lock_print(f"{img_mp} * {shrink_factor} → {_hres}x{_vres} 
({_hres*_vres/1e6}MP)")
+
+        img = gamma_aware_resize(img, (_hres, _vres), antialias)
+        if debug:
+            lock_print('resize_image: scaled to [%dx%d]' % (_hres, _vres))
+
     elif scale:
         if scale == 100:
             _hres = hres
@@ -294,9 +362,10 @@
                 _vres = _vres + 1
             _vres = int(_vres)
 
-        img = img.resize((_hres, _vres), antialias)
+        img = gamma_aware_resize(img, (_hres, _vres), antialias)
         if debug:
             lock_print('resize_image: scaled to [%dx%d]' % (_hres, _vres))
+
     elif HRES == 0 or VRES == 0:
         if HRES:
             if not enlarge and HRES > hres:
@@ -310,7 +379,7 @@
                 target_vres = target_vres + 1
             target_vres = int(target_vres)
 
-            img = img.resize((HRES, target_vres), antialias)
+            img = gamma_aware_resize(img, (HRES, target_vres), antialias)
             if debug:
                 lock_print('resize_image: 0 vres %dx%d' % (HRES, target_vres))
 
@@ -326,16 +395,19 @@
                 target_hres = target_hres + 1
             target_hres = int(target_hres)
 
-            img = img.resize((target_hres, VRES), antialias)
+            img = gamma_aware_resize(img, (target_hres, VRES), antialias)
             if debug:
                 lock_print('resize_image: 0 hres %dx%d' % (target_hres, VRES))
+
     elif HRES == hres and VRES == vres:
         if debug:
             lock_print('resize_image: same res [%dx%d]' % (HRES, VRES))
+
     elif force:
-        img = img.resize((HRES, VRES), antialias)
+        img = gamma_aware_resize(img, (HRES, VRES), antialias)
         if debug:
             lock_print('resize_image: brute force res to [%dx%d]' % (HRES, 
VRES))
+
     elif adapt:
         ratio_img = float(hres/vres)
         ratio_target = float(HRES/VRES)
@@ -355,7 +427,7 @@
                     target_vres = target_vres + 1
                 target_vres = int(target_vres)
 
-                img = img.resize((HRES, target_vres), antialias)
+                img = gamma_aware_resize(img, (HRES, target_vres), antialias)
                 if debug:
                     lock_print('resize_image: vres adapted %dx%d' % (HRES, 
target_vres))
         elif (ratio_target < 1 and ratio_img < 1) or (ratio_target >= 1 and 
ratio_img <= 1):
@@ -373,9 +445,10 @@
                     target_hres = target_hres + 1
                 target_hres = int(target_hres)
 
-                img = img.resize((target_hres, VRES), antialias)
+                img = gamma_aware_resize(img, (target_hres, VRES), antialias)
                 if debug:
                     lock_print('resize_image: hres adapted %dx%d' % 
(target_hres, VRES))
+
     else:
         ratio_img = float(hres/vres)
         ratio_target = float(HRES/VRES)
@@ -393,7 +466,7 @@
                 target_vres = target_vres + 1
             target_vres = int(target_vres)
 
-            img = img.resize((HRES, target_vres), antialias)
+            img = gamma_aware_resize(img, (HRES, target_vres), antialias)
             if debug:
                 lock_print('resize_image: vres calculated %dx%d' % (HRES, 
target_vres))
         else:
@@ -409,7 +482,7 @@
                 target_hres = target_hres + 1
             target_hres = int(target_hres)
 
-            img = img.resize((target_hres, VRES), antialias)
+            img = gamma_aware_resize(img, (target_hres, VRES), antialias)
             if debug:
                 lock_print('resize_image: hres calculated %dx%d' % 
(target_hres, VRES))
 
@@ -423,7 +496,7 @@
 
     if convert and _format == 'PNG':
         if img.mode in ('RGBA', 'LA'):
-            background = Image.new(img.mode[:-1], img.size, fill_color)
+            background = Image.new(img.mode.rstrip('A'), img.size, fill_color)
             background.paste(img, img.split()[-1])
             img = background
         elif img.mode == "P":
@@ -445,8 +518,7 @@
 
         return [1, 0]
     except OSError as e:
-        with lock:
-            print(e)
+        lock_print(e)
 
 
 class ExtendedArgumentParser(argparse.ArgumentParser):
@@ -460,8 +532,8 @@
 
         file.write('''
 Version %s
-Copyright © 2016-2023 Arun Prakash Jana <[email protected]>
-License: GPLv3
+Copyright © 2016-2026 Arun Prakash Jana <[email protected]>
+License: GPL-3.0-or-later
 Webpage: https://github.com/jarun/imgp
 ''' % _VERSION_)
 
@@ -489,7 +561,7 @@
                                        
formatter_class=argparse.RawTextHelpFormatter)
     addarg = argparser.add_argument
     addarg('-x', '--res', nargs=1, metavar='res',
-           help='output resolution in HxV or percentage')
+           help='output resolution in HxV or percentage ("%%" suffix) or 
megapixel ("MP" suffix)')
     addarg('-o', '--rotate', type=int, metavar='deg',
            help='rotate clockwise by angle (in degrees)')
     addarg('-a', '--adapt', action='store_true',
@@ -521,15 +593,16 @@
     addarg('-P', '--progressive', action='store_true',
            help='save JPEG images as progressive [default: off]')
     addarg('-q', '--quality', type=int, metavar='N', choices=range(1, 96),
-           help='quality factor (N=1-95, JPEG only) [default: 75]')
+           help=f'quality factor (N=1-95, JPEG only) [default: {qual}]')
     addarg('-r', '--recurse', action='store_true',
            help='process non-symbolic dirs recursively [default: off]')
     addarg('-s', '--size', type=int, metavar='byte',
-           help='minimum size to process an image [default: 1024]')
+           help=f'minimum size to process an image [default: {size}]')
     addarg('-w', '--overwrite', action='store_true',
            help='overwrite source images [default: off]')
     addarg('-d', '--debug', action='store_true',
            help='enable debug logs [default: off]')
+    addarg('-v', '--version', action='version', version=_VERSION_)
     addarg('keywords', nargs='*', metavar='PATH',
            help='source file or dir [default: current dir]')
 
@@ -542,10 +615,11 @@
 
 
 def main():
-    global HRES, VRES, scale, rotate, adapt, convert, hidden, eraseexif, 
force, \
+    global HRES, VRES, megapixel, scale, rotate, adapt, convert, hidden, 
eraseexif, force, \
            includeimgp, keep, enlarge, optimal, progressive, mute, recurse, 
size, \
-           qual, overwrite, debug, no_res_opt, pool, init_time, count, png_ip, 
minH, minV
+           qual, overwrite, debug, no_res_opt, pool, count, png_ip, minH, minV
 
+    init_time = time.time()  # profile the total time taken
     args = parse_args()
 
     if args.rotate is not None and args.res is not None:
@@ -556,15 +630,18 @@
         if 'x' in args.res[0]:
             HRES, VRES = getres(args.res[0])
             if HRES == 0 and VRES == 0:
-                print('-ve values not allowed in --res, \
-                       hres and vres cannot be 0 together')
+                print('-ve values not allowed in --res, '
+                      'hres and vres cannot be 0 together')
+                return
+        elif args.res[0].upper().endswith("MP"):
+            try:
+                megapixel = float(args.res[0].upper().rstrip("MP"))
+            except ValueError:
+                print(f'failed parsing megapixel value from "{args.res[0]}"')
                 return
         else:
             try:
-                if args.res[0].endswith('%'):
-                    scale = int(args.res[0][:-1])
-                else:
-                    scale = int(args.res[0])
+                scale = int(args.res[0].rstrip("%"))
 
                 if scale <= 0:
                     print('scale should be > 0%')
@@ -572,8 +649,8 @@
                 if scale > 100 and not args.enlarge:
                     print('use --enlarge to scale > 100%')
                     return
-            except Exception:
-                print('invalid value for --res')
+            except ValueError:
+                print(f'invalid value for --res: "{args.res[0]}"')
                 return
     elif args.rotate is not None:
         rotate = args.rotate % 360
@@ -591,8 +668,8 @@
         if 'x' in args.minres[0]:
             minH, minV = getres(args.minres[0])
             if minH == 0 or minV == 0:
-                print('-ve values not allowed in --minres, \
-                       H or V cannot be 0')
+                print('-ve values not allowed in --minres, '
+                      'H or V cannot be 0')
                 return
         else:
             if scale is not None:
@@ -600,10 +677,7 @@
                 return
 
             try:
-                if args.minres[0].endswith('%'):
-                    minscale = int(args.minres[0][:-1])
-                else:
-                    minscale = int(args.minres[0])
+                minscale = int(args.minres[0].rstrip('%'))
 
                 if minscale <= 0:
                     print('--minres should be > 0%')
@@ -662,7 +736,7 @@
     if not mute:
         # Show number of images converted
         if count:
-            print('%s processed in %.4f seconds.' % (count, time.time() - 
init_time))
+            print('%s images processed in %.4f seconds.' % (count, time.time() 
- init_time))
 
         # Show number of images not converted due to low resolution
         if count_lowres:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/imgp-2.9/imgp.1 new/imgp-3.0/imgp.1
--- old/imgp-2.9/imgp.1 2023-09-10 05:42:25.000000000 +0200
+++ new/imgp-3.0/imgp.1 2026-08-16 08:52:22.000000000 +0200
@@ -1,4 +1,4 @@
-.TH "IMGP" "1" "10 Sep 2023" "Version 2.9" "User Commands"
+.TH "IMGP" "1" "16 Aug 2026" "Version 3.0" "User Commands"
 .SH NAME
 imgp \- Resize, rotate JPEG and PNG images.
 .SH SYNOPSIS
@@ -53,7 +53,7 @@
 Show help text and exit.
 .TP
 .BI "-x, --res=" res
-Output resolution in HRESxVRES or percentage.
+Output resolution in HRESxVRES or percentage ("%" suffix) or megapixel ("MP" 
suffix).
 .TP
 .BI "-o, --rotate=" deg
 Rotate clockwise by a specified angle (in degrees). Negative inputs rotate 
anti-clockwise. Rotation by 0 degree is not allowed.
@@ -114,6 +114,9 @@
 .TP
 .BI "-d, --debug"
 Enable debugging.
+.TP
+.BI "-v, --version"
+Show the program version and exit.
 .SH EXAMPLES
 .PP
 .IP 1. 4
@@ -151,6 +154,17 @@
 .EE
 .PP
 .IP 3. 4
+Ensure all images in folder `testphotos/` (contains 4 images) are 12MP at 
maximum:
+.PP
+.EX
+.IP
+.B $ imgp --res 12MP -w testphotos/
+testphotos/20250111_193949_ILCE-7M3.jpg
+6000x4000 -> 4243x2828
+8355840 bytes -> 3545149 bytes
+.EE
+.PP
+.IP 4. 4
 Rotate an image clockwise by 90 degrees:
 .PP
 .EX
@@ -159,7 +173,7 @@
 120968 bytes -> 72038 bytes
 .EE
 .PP
-.IP 4. 4
+.IP 5. 4
 Adapt the images in the current directory to 1366x1000 resolution.
 .br
 Visit all directories recursively, overwrite source images, ignore images with 
matching hres or vres but convert PNG images to JPEG.
@@ -169,7 +183,7 @@
 .B $ imgp -x 1366x1000 -wrack
 .EE
 .PP
-.IP 5. 4
+.IP 6. 4
 Set hres=800 and adapt vres maintaining the ratio.
 .PP
 .EX
@@ -186,7 +200,7 @@
 458092 bytes -> 78089 bytes
 .EE
 .PP
-.IP 6. 4
+.IP 7. 4
 Process images greater than 50KiB only:
 .PP
 .EX
@@ -194,7 +208,7 @@
 .B $ imgp -wrackx 1366x1000 -s 51200
 .EE
 .PP
-.IP 7. 4
+.IP 8. 4
 Generate a 64px adaptive thumbnail of the last modified file in the current 
dir:
 .PP
 .EX
@@ -214,8 +228,8 @@
 .SH REPORTING BUGS
 .I https://github.com/jarun/imgp/issues
 .SH LICENSE
-Copyright \(co 2016-2023 Arun Prakash Jana <[email protected]>
+Copyright \(co 2016-2026 Arun Prakash Jana <[email protected]>
 .PP
-License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.
+License GPL-3.0-or-later+: GNU GPL version 3 or later 
<http://gnu.org/licenses/gpl.html>.
 .br
 This is free software: you are free to change and redistribute it. There is NO 
WARRANTY, to the extent permitted by law.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/imgp-2.9/packagecore.yaml 
new/imgp-3.0/packagecore.yaml
--- old/imgp-2.9/packagecore.yaml       2023-09-10 05:42:25.000000000 +0200
+++ new/imgp-3.0/packagecore.yaml       1970-01-01 01:00:00.000000000 +0100
@@ -1,105 +0,0 @@
-name: imgp
-maintainer: Arun Prakash Jana <[email protected]>
-license: GPLv3
-summary: High-performance cli batch image resizer and rotator.
-homepage: https://github.com/jarun/imgp
-commands:
-  install:
-    - make PREFIX="/usr" install DESTDIR="${BP_DESTDIR}"
-packages:
-  centos7.5:
-    builddeps:
-      - make
-    deps:
-      - python-pillow
-      - python
-    commands:
-      pre:
-        - yum install epel-release
-  centos7.6:
-    builddeps:
-      - make
-    deps:
-      - python-pillow
-      - python
-    commands:
-      pre:
-        - yum install epel-release
-  centos7.7:
-    builddeps:
-      - make
-    deps:
-      - python-pillow
-      - python
-    commands:
-      pre:
-        - yum install epel-release
-  centos8.0:
-    builddeps:
-      - make
-    deps:
-      - python3-pillow
-      - python3
-    commands:
-      precompile:
-        - dnf install python3
-  debian9:
-    builddeps:
-      - make
-    deps:
-      - python3-pillow
-      - python3
-  debian10:
-    builddeps:
-      - make
-    deps:
-      - python3-pillow
-      - python3
-  fedora31:
-    builddeps:
-      - make
-    deps:
-      - python3-pillow
-      - python3
-  fedora32:
-    builddeps:
-      - make
-    deps:
-      - python3-pillow
-      - python3
-  opensuse15.1:
-    builddeps:
-      - make
-    deps:
-      - python3-Pillow
-      - python3
-  opensuse15.2:
-    builddeps:
-      - make
-    deps:
-      - python3-Pillow
-      - python3
-  opensuse.tumbleweed:
-    builddeps:
-      - make
-    deps:
-      - python3-Pillow
-      - python3
-  ubuntu16.04:
-    builddeps:
-      - make
-    deps:
-      - python3-pillow
-      - python3
-  ubuntu18.04:
-    builddeps:
-      - make
-    deps:
-      - python3-pillow
-      - python3
-  ubuntu20.04:
-    builddeps:
-      - make
-    deps:
-      - python3-pillow
-      - python3
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/imgp-2.9/setup.py new/imgp-3.0/setup.py
--- old/imgp-2.9/setup.py       2023-09-10 05:42:25.000000000 +0200
+++ new/imgp-3.0/setup.py       2026-08-16 08:52:22.000000000 +0200
@@ -4,14 +4,14 @@
 import os.path
 import shutil
 import sys
+from importlib.metadata import PackageNotFoundError, distribution
 
 from setuptools import setup, find_packages
-from pkg_resources import get_distribution, DistributionNotFound
 
 def get_dist(pkgname):
     try:
-        return get_distribution(pkgname)
-    except DistributionNotFound:
+        return distribution(pkgname)
+    except PackageNotFoundError:
         return None
 
 if os.path.isfile('imgp'):
@@ -34,9 +34,9 @@
     author='Arun Prakash Jana',
     author_email='[email protected]',
     url='https://github.com/jarun/imgp',
-    license='GPLv3',
+    license='GPL-3.0-or-later',
     license_file='LICENSE',
-    python_requires='>=3.8',  # requires pip>=9.0.0
+    python_requires='>=3.10',  # requires pip>=9.0.0
     platforms=['any'],
     py_modules=['imgp'],
     install_requires=requirements,
@@ -53,15 +53,16 @@
         'Environment :: Console',
         'Intended Audience :: Developers',
         'Intended Audience :: End Users/Desktop',
-        'License :: OSI Approved :: GNU General Public License v3 (GPLv3)',
+        'License :: OSI Approved :: GNU General Public License v3 or later 
(GPLv3+)',
         'Natural Language :: English',
         'Operating System :: OS Independent',
         'Programming Language :: Python :: 3',
         'Programming Language :: Python :: 3 :: Only',
-        'Programming Language :: Python :: 3.8',
-        'Programming Language :: Python :: 3.9',
         'Programming Language :: Python :: 3.10',
         'Programming Language :: Python :: 3.11',
+        'Programming Language :: Python :: 3.12',
+        'Programming Language :: Python :: 3.13',
+        'Programming Language :: Python :: 3.14',
         'Topic :: Utilities'
     ]
 )

Reply via email to