Skip to content

Commit

Permalink
🦀 (Rust): Expose metadata compress and disable metadata compress by d…
Browse files Browse the repository at this point in the history
…efault (#87)
  • Loading branch information
Isotr0py authored Dec 1, 2024
1 parent e022704 commit 0b4e2b7
Show file tree
Hide file tree
Showing 11 changed files with 64 additions and 7 deletions.
1 change: 1 addition & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ jobs:
- name: Test with pytest
run: |
brew install inih
source venv/bin/activate
pip install -e .[dev]
pytest test/ --junitxml=junit/test-results-${{ matrix.python-version }}.xml
Expand Down
2 changes: 2 additions & 0 deletions pillow_jxl/JpegXLImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ def _save(im, fp, filename, save_all=False):
use_original_profile = info.get("use_original_profile", False)
jpeg_encode = info.get("lossless_jpeg", None)
num_threads = info.get("num_threads", -1)
compress_metadata = info.get("compress_metadata", False)

enc = Encoder(
mode=im.mode,
Expand Down Expand Up @@ -140,6 +141,7 @@ def _save(im, fp, filename, save_all=False):
"exif": exif or None,
"jumb": info.get("jumb") or None,
"xmp": info.get("xmp") or None,
"compress": compress_metadata,
}
data = enc(im.tobytes(), im.width, im.height, jpeg_encode=False, **metadata)
fp.write(data)
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ dependencies = [
]

[project.optional-dependencies]
dev = ["numpy", "pytest"]
dev = ["numpy", "pytest", "pyexiv2"]

[project.urls]
"Homepage" = "https://github.com/Isotr0py/pillow-jpegxl-plugin"
Expand Down
14 changes: 9 additions & 5 deletions src/encode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ impl Encoder {
})
}

#[pyo3(signature = (data, width, height, jpeg_encode, exif=None, jumb=None, xmp=None))]
#[pyo3(signature = (data, width, height, jpeg_encode, exif=None, jumb=None, xmp=None, compress=false))]
fn __call__(
&self,
py: Python,
Expand All @@ -84,8 +84,11 @@ impl Encoder {
exif: Option<&[u8]>,
jumb: Option<&[u8]>,
xmp: Option<&[u8]>,
compress: bool,
) -> PyResult<Cow<'_, [u8]>> {
py.allow_threads(|| self.call_inner(data, width, height, jpeg_encode, exif, jumb, xmp))
py.allow_threads(|| {
self.call_inner(data, width, height, jpeg_encode, exif, jumb, xmp, compress)
})
}

fn __repr__(&self) -> PyResult<String> {
Expand All @@ -106,6 +109,7 @@ impl Encoder {
exif: Option<&[u8]>,
jumb: Option<&[u8]>,
xmp: Option<&[u8]>,
compress: bool,
) -> PyResult<Cow<'_, [u8]>> {
let parallel_runner = ThreadsRunner::new(
None,
Expand Down Expand Up @@ -149,17 +153,17 @@ impl Encoder {
let frame = EncoderFrame::new(data).num_channels(self.num_channels);
if let Some(exif_data) = exif {
encoder
.add_metadata(&Metadata::Exif(exif_data), true)
.add_metadata(&Metadata::Exif(exif_data), compress)
.map_err(to_pyjxlerror)?
}
if let Some(xmp_data) = xmp {
encoder
.add_metadata(&Metadata::Xmp(xmp_data), true)
.add_metadata(&Metadata::Xmp(xmp_data), compress)
.map_err(to_pyjxlerror)?
}
if let Some(jumb_data) = jumb {
encoder
.add_metadata(&Metadata::Jumb(jumb_data), true)
.add_metadata(&Metadata::Jumb(jumb_data), compress)
.map_err(to_pyjxlerror)?
}
encoder
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@ create_exception!(my_module, JxlException, PyRuntimeError, "Jxl Error");
fn pillow_jxl(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<decode::Decoder>()?;
m.add_class::<encode::Encoder>()?;
m.add("JxlException", m.py().get_type_bound::<JxlException>())?;
m.add("JxlException", m.py().get_type::<JxlException>())?;
Ok(())
}
Binary file added test/images/metadata/1x1_exif_xmp.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/images/metadata/1x1_exif_xmp.jxl
Binary file not shown.
Binary file added test/images/metadata/1x1_exif_xmp.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/images/metadata/sample.exif
Binary file not shown.
1 change: 1 addition & 0 deletions test/images/metadata/sample.xmp
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?> <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 4.4.0-Exiv2"> <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> <rdf:Description rdf:about="" xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/" xmlns:stEvt="http://ns.adobe.com/xap/1.0/sType/ResourceEvent#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:GIMP="http://www.gimp.org/xmp/" xmlns:xmp="http://ns.adobe.com/xap/1.0/" xmpMM:DocumentID="gimp:docid:gimp:10093e7e-33ea-4901-a7ce-a3c04b8b6480" xmpMM:InstanceID="xmp.iid:737430f3-5b4d-49cf-9873-c10684618917" xmpMM:OriginalDocumentID="xmp.did:f7302aac-e69b-4ad8-ac19-58a76e2fc4f7" dc:Format="image/jpeg" GIMP:API="2.0" GIMP:Platform="Linux" GIMP:TimeStamp="1635353959433667" GIMP:Version="2.10.28" xmp:CreatorTool="GIMP 2.10"> <xmpMM:History> <rdf:Seq> <rdf:li stEvt:action="saved" stEvt:changed="/metadata" stEvt:instanceID="xmp.iid:735c4bc4-11be-4e71-8aad-0ddd90c9607d" stEvt:softwareAgent="Gimp 2.10 (Linux)" stEvt:when="2021-10-27T18:58:46+02:00"/> <rdf:li stEvt:action="saved" stEvt:changed="/" stEvt:instanceID="xmp.iid:70d81176-171d-43b8-b687-1eb80d302337" stEvt:softwareAgent="Gimp 2.10 (Linux)" stEvt:when="2021-10-27T18:59:19+02:00"/> </rdf:Seq> </xmpMM:History> <dc:title> <rdf:Alt> <rdf:li xml:lang="x-default">test</rdf:li> </rdf:Alt> </dc:title> </rdf:Description> </rdf:RDF> </x:xmpmeta> <?xpacket end="w"?>
49 changes: 49 additions & 0 deletions test/test_plugin.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import tempfile

import pyexiv2
import pytest
import numpy as np
from PIL import Image
Expand Down Expand Up @@ -75,3 +76,51 @@ def test_icc_profile():
assert img_ori.size == img_jxl.size
assert img_ori.mode == img_jxl.mode
assert img_ori.info["icc_profile"] == img_jxl.info["icc_profile"]


def test_metadata_decode():
# Load a JPEG image
img_ori = Image.open("test/images/metadata/1x1_exif_xmp.jpg")
img_jxl = Image.open("test/images/metadata/1x1_exif_xmp.jxl")
assert img_ori.getexif() == img_jxl.getexif()


def test_metadata_encode_from_jpg():
# Load a JPEG image
ref_img_path = "test/images/metadata/1x1_exif_xmp.jpg"
temp = tempfile.mktemp(suffix=".jxl")
img_ori = Image.open(ref_img_path)
img_ori.save(temp, use_container=True)

img_enc = Image.open(temp)
img_enc_exiv2 = pyexiv2.Image(temp)
img_ori_exiv2 = pyexiv2.Image(ref_img_path)
assert img_ori.getexif() == img_enc.getexif()
assert img_ori_exiv2.read_exif() == img_enc_exiv2.read_exif()


def test_metadata_encode_from_raw_exif():
with open("test/images/metadata/sample.exif", "rb") as f:
ref_exif = f.read()
img_ori = Image.open("test/images/sample.png")
temp = tempfile.mktemp(suffix=".jxl")
img_ori.save(temp, exif=ref_exif)

ref_exif = pyexiv2.ImageData(ref_exif).read_exif()
jxl_exif = pyexiv2.Image(temp).read_exif()
assert ref_exif == jxl_exif


def test_metadata_encode_from_pil_exif():
exif_img_path = "test/images/metadata/1x1_exif_xmp.jpg"
dummy_img = Image.open("test/images/sample.png")
exif_img = Image.open(exif_img_path)
temp = tempfile.mktemp(suffix=".jxl")
dummy_img.save(temp, exif=exif_img.getexif().tobytes())

ref_exif = pyexiv2.Image(exif_img_path).read_exif()
jxl_exif = pyexiv2.Image(temp).read_exif()
for key in ref_exif:
# Skip UserComment and GPSAltitude as they are broken
if key not in ("Exif.Photo.UserComment", 'Exif.GPSInfo.GPSAltitude'):
assert ref_exif[key] == jxl_exif[key]

0 comments on commit 0b4e2b7

Please sign in to comment.