Image loading now respects exif image rotation
|
|
@ -21,17 +21,63 @@ from urllib import request
|
|||
|
||||
from PyQt6 import QtGui
|
||||
|
||||
import exif
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def exif_rotated_image(path=None):
|
||||
"""Returns a QImage that is transformed according to the source's
|
||||
orientation EXIF data.
|
||||
"""
|
||||
|
||||
img = QtGui.QImage(path)
|
||||
if img.isNull():
|
||||
return img
|
||||
|
||||
with open(path, 'rb') as f:
|
||||
exifimg = exif.Image(f)
|
||||
|
||||
if 'orientation' in exifimg.list_all():
|
||||
orientation = exifimg.orientation
|
||||
else:
|
||||
return img
|
||||
|
||||
transform = QtGui.QTransform()
|
||||
|
||||
if orientation == exif.Orientation.TOP_RIGHT:
|
||||
return img.mirrored(horizontal=True, vertical=False)
|
||||
if orientation == exif.Orientation.BOTTOM_RIGHT:
|
||||
transform.rotate(180)
|
||||
return img.transformed(transform)
|
||||
if orientation == exif.Orientation.BOTTOM_LEFT:
|
||||
return img.mirrored(horizontal=False, vertical=True)
|
||||
if orientation == exif.Orientation.LEFT_TOP:
|
||||
transform.rotate(90)
|
||||
return img.transformed(transform).mirrored(
|
||||
horizontal=True, vertical=False)
|
||||
if orientation == exif.Orientation.RIGHT_TOP:
|
||||
transform.rotate(90)
|
||||
return img.transformed(transform)
|
||||
if orientation == exif.Orientation.RIGHT_BOTTOM:
|
||||
transform.rotate(270)
|
||||
return img.transformed(transform).mirrored(
|
||||
horizontal=True, vertical=False)
|
||||
if orientation == exif.Orientation.LEFT_BOTTOM:
|
||||
transform.rotate(270)
|
||||
return img.transformed(transform)
|
||||
|
||||
return img
|
||||
|
||||
|
||||
def load_image(path):
|
||||
if isinstance(path, str):
|
||||
return (QtGui.QImage(path), path)
|
||||
return (exif_rotated_image(path), path)
|
||||
if path.isLocalFile():
|
||||
return (QtGui.QImage(path.path()), path.path())
|
||||
return (exif_rotated_image(path.path()), path.path())
|
||||
|
||||
img = QtGui.QImage()
|
||||
img = exif_rotated_image()
|
||||
try:
|
||||
imgdata = request.urlopen(path.url()).read()
|
||||
except URLError as e:
|
||||
|
|
@ -42,5 +88,5 @@ def load_image(path):
|
|||
with open(fname, 'wb') as f:
|
||||
f.write(imgdata)
|
||||
logger.debug(f'Temporarily saved in: {fname}')
|
||||
img = QtGui.QImage(fname)
|
||||
img = exif_rotated_image(fname)
|
||||
return (img, path.url())
|
||||
|
|
|
|||
1
setup.py
|
|
@ -11,6 +11,7 @@ setup(
|
|||
install_requires=[
|
||||
'pyQt6>=6.1',
|
||||
'rectangle-packer>=2.0.1',
|
||||
'exif',
|
||||
],
|
||||
packages=['beeref'],
|
||||
entry_points={
|
||||
|
|
|
|||
BIN
tests/assets/test3x3.jpg
Normal file
|
After Width: | Height: | Size: 888 B |
|
Before Width: | Height: | Size: 158 B After Width: | Height: | Size: 3.1 KiB |
BIN
tests/assets/test3x3_orientation1.jpg
Normal file
|
After Width: | Height: | Size: 960 B |
BIN
tests/assets/test3x3_orientation2.jpg
Normal file
|
After Width: | Height: | Size: 959 B |
BIN
tests/assets/test3x3_orientation3.jpg
Normal file
|
After Width: | Height: | Size: 965 B |
BIN
tests/assets/test3x3_orientation4.jpg
Normal file
|
After Width: | Height: | Size: 964 B |
BIN
tests/assets/test3x3_orientation5.jpg
Normal file
|
After Width: | Height: | Size: 958 B |
BIN
tests/assets/test3x3_orientation6.jpg
Normal file
|
After Width: | Height: | Size: 958 B |
BIN
tests/assets/test3x3_orientation7.jpg
Normal file
|
After Width: | Height: | Size: 966 B |
BIN
tests/assets/test3x3_orientation8.jpg
Normal file
|
After Width: | Height: | Size: 963 B |
|
|
@ -1,8 +1,52 @@
|
|||
import math
|
||||
import os.path
|
||||
|
||||
import httpretty
|
||||
import pytest
|
||||
|
||||
from PyQt6 import QtCore
|
||||
from PyQt6 import QtCore, QtGui
|
||||
|
||||
from beeref.fileio.image import load_image
|
||||
from beeref.fileio.image import exif_rotated_image, load_image
|
||||
|
||||
|
||||
def test_exif_rotated_image_without_path(qapp):
|
||||
img = exif_rotated_image()
|
||||
assert img.isNull() is True
|
||||
|
||||
|
||||
def test_exif_rotated_image_not_a_file(qapp):
|
||||
img = exif_rotated_image('foo')
|
||||
assert img.isNull() is True
|
||||
|
||||
|
||||
@pytest.mark.parametrize('path,expected',
|
||||
[('test3x3.png', 'test3x3.png'),
|
||||
('test3x3_orientation1.jpg', 'test3x3.jpg'),
|
||||
('test3x3_orientation2.jpg', 'test3x3.jpg'),
|
||||
('test3x3_orientation3.jpg', 'test3x3.jpg'),
|
||||
('test3x3_orientation4.jpg', 'test3x3.jpg'),
|
||||
('test3x3_orientation5.jpg', 'test3x3.jpg'),
|
||||
('test3x3_orientation6.jpg', 'test3x3.jpg'),
|
||||
('test3x3_orientation7.jpg', 'test3x3.jpg'),
|
||||
('test3x3_orientation8.jpg', 'test3x3.jpg')])
|
||||
def test_exif_rotated_image(path, expected, qapp):
|
||||
def get_fname(p):
|
||||
root = os.path.dirname(__file__)
|
||||
return os.path.join(root, '..', 'assets', p)
|
||||
|
||||
img = exif_rotated_image(get_fname(path))
|
||||
assert img.isNull() is False
|
||||
expected = QtGui.QImage(get_fname(expected))
|
||||
assert expected.isNull() is False
|
||||
|
||||
# The JPEG format isn't pixel perfect, so we have to check whether
|
||||
# pixels are approximately the same:
|
||||
for x in range(3):
|
||||
for y in range(3):
|
||||
col_img = img.pixelColor(x, y).getRgb()
|
||||
col_expected = expected.pixelColor(x, y).getRgb()
|
||||
diff = [(col_img[i] - col_expected[i])**2 for i in range(4)]
|
||||
assert math.sqrt(sum(diff)) < 3
|
||||
|
||||
|
||||
def test_load_image_loads_from_filename(view, imgfilename3x3):
|
||||
|
|
|
|||