From 8b0252f987842bb6d327c4077bf9e75905d87c86 Mon Sep 17 00:00:00 2001 From: relikd Date: Sat, 28 Feb 2026 22:28:06 +0100 Subject: [PATCH] fix: jp2 image size bytes header --- icnsutil/RawData.py | 35 +++++++++++++++++++++++++++-------- tests/fixtures/32x32.jpf | Bin 0 -> 2009 bytes tests/test_cli.py | 1 + tests/test_icnsutil.py | 1 + 4 files changed, 29 insertions(+), 8 deletions(-) create mode 100644 tests/fixtures/32x32.jpf diff --git a/icnsutil/RawData.py b/icnsutil/RawData.py index 0f42ccc..7d31404 100644 --- a/icnsutil/RawData.py +++ b/icnsutil/RawData.py @@ -29,6 +29,32 @@ def determine_file_ext(data: bytes) -> Optional[str]: return None +def _determine_jp2_size(data: bytes) -> Optional[Tuple[int, int]]: + ''' Read raw bytes and extract JPEG2000 image size. ''' + if data[:4] == b'\xFF\x4F\xFF\x51': + w, h = struct.unpack('>II', data[8:16]) + return w, h + + # fixed "jP" file header 0000000C 6A502020 0D0A870A + off = 12 + filesize = len(data) + + while off < filesize: + box_size, box_type = struct.unpack('>I4s', data[off:off+8]) + # find JP2 Header box + if box_type == b'jp2h': + child = off + 8 # skip parent header + while child < (off + box_size): + # find Image Header box + if data[child+4:child+8] == b'ihdr': + h, w = struct.unpack('>II', data[child+8:child+16]) + return w, h + + child += struct.unpack('>I', data[child:child+4])[0] + off += box_size + return None + + def determine_image_size(data: bytes, ext: Optional[str] = None) \ -> Optional[Tuple[int, int]]: ''' Supports PNG, ARGB, and Jpeg 2000 image data. ''' @@ -45,14 +71,7 @@ def determine_image_size(data: bytes, ext: Optional[str] = None) \ data = data[4:] # without it32 header return IcnsType.match_maxsize(PackBytes.get_size(data), 'rgb').size elif ext == 'jp2': - if data[:4] == b'\xFF\x4F\xFF\x51': - w, h = struct.unpack('>II', data[8:16]) - return w, h - len_ftype = struct.unpack('>I', data[12:16])[0] - # file header + type box + header box (super box) + image header box - offset = 12 + len_ftype + 8 + 8 - h, w = struct.unpack('>II', data[offset:offset + 8]) - return w, h + return _determine_jp2_size(data) return None # icns does not support other image types except binary diff --git a/tests/fixtures/32x32.jpf b/tests/fixtures/32x32.jpf new file mode 100644 index 0000000000000000000000000000000000000000..6d25a0e63c88f5d42fe9508713c6900f0b4ef31e GIT binary patch literal 2009 zcmZQzVBpCLP*C9IYUg5LV30{GsVvAUFj8P(U|;~zSp^kISp^j!er{1wY9Z7A|35x3 zure?G%)ZeI4}qaC@|=92r#f%@iIwpy>t}aT;_es@f=?v3yb?noii5~x;E}) zvDh7wUTDq2vd49K$=v|IV=KRXUTFTMZUPI-^%F^F%J9|V1q%xU1A}{3 zfl&qn1A|y*MoJL_0|Uqb3JlEb?2I6n@h9i!6frV@*a8d;3{pj@#UKSjMXANf+(HbD zAPiE+AX!?PnS$;tkPKJ?-E|-d8ITTiM=~%l7=c0uB!})?s2nJ?lK=bv4`k2>83T1N zmxC3ZsW zgwQ#mo}r$;MZOKb4Q_&N$K8$_NZJ0k{cmasQpMzy-WcB>-yc3V+%DYCKi9w3zt%#~ zqROJm(A$9BfZbgC|K!6Tm^dpAusbpwV0YwzJ4Tj~?f>M411tuNJPa)i46FxO6d2%= zx=d{UCokY=U{5)~v4F3Y!G)QPWgBZZl*1>;a+YT&TyY{Z+yBW2N-|gigct<>K6r4y zg581ZliQ{PH7X7@Dh@w9MBUrvb&sC;#Q9NDLT-kS4Zpqbt4pEX-e3jF2VOi7nU_8> z@t9fwV|>7R#t9z6;t9sS@;xvM7`<3P7QDLflgIgC0@HKlkzoGYg-F{44pvsfaeQ3sdk&|=Y@Lv?V=(5Se zR48adI<8gc_*fG^L@GWo^j#&n_1784nOgfx2pIl%pmwb|Fmw(pUW+7$L?<3RCaFeWs#ca zf-cW@{0^EZ;j*|@X4Cg8%I;9<4pew7u@^v0sjX=J!OSUL^moNd7ecH!LKX7qYVb zpM3b?j{Zp}`R+H!6xi)x+WfDO?bM;SP9MyCikD=V^V-Z5)wa*u8UAQx=rPsTufu+^ z)qLDNbEmbA_BHO=UORh~?zM?%RBzKI zU_UTjz-XVSz;q3N8>z2LFIe6*&Jo$c5|q4k#ig}o{eI6J{ zaJSv7yZcja0pkKUyL|ORj*_!i>x+d)kwT7g`Tz@b0~{Rb59IxGP#Tz(Sc&(G~&3|gWeD;T@ohtep zu6XUfI;B*o>+_do4VO7m6Btw&XFk!>wy}5=^szs~=28mhPp0Ql2~2zH*Vb~)QaiZB z$GgeI$n@}%fT>RotNgc6)nsO0V*13R&h$y@n=N0pIEob7{&cGI9xiy6oN>$H-J}~m ztFy$n?)?7CQ@?9L5!>;T$@^9%*?CKdx^G}yd%(mHpt`Lz(@{*>WbG{>8$;%XRnMs?{@DPOnyHX8mH113y12f&UL5NXsW0QMzjllwPP zmZW&T+ubl}v;5T=vlvWGcg}aYS#Wt>*6)rU!_@&|f8Bbo9}@^*IdD$q%$fC?_A3{5 z8XL02{uk&F^7#9oT{%zJqulJ#?ZOu^U*F{(?Y7oGEEC;bc)0GvgqwTThqW|3DLSdy zs>O6i{rbaGEQL=lec|=5mTWLjC=6oD6O`_ClQff;cj}A(A-u0UHNkO4&TXF6PJY)J zJnK3*xO0PM i%QlG2OA66+X`aXy_(E!ZhRBvDo8>xx>)u`X|0V!S)FU7O literal 0 HcmV?d00001 diff --git a/tests/test_cli.py b/tests/test_cli.py index da7ae0b..b579ff3 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -146,6 +146,7 @@ class TestCLI_compose(unittest.TestCase): def test_jp2(self): self.assert_conv_file('256x256.jp2', 'ic08') self.assert_conv_file('18x18.j2k', 'icsb', arg=['-f']) + self.assert_conv_file('32x32.jpf', 'icp5', arg=['-f']) def test_argb(self): self.assert_conv_file('rgb.icns.argb', 'ic04') diff --git a/tests/test_icnsutil.py b/tests/test_icnsutil.py index 5dd0a0a..b09fce3 100644 --- a/tests/test_icnsutil.py +++ b/tests/test_icnsutil.py @@ -444,6 +444,7 @@ class TestRawData(unittest.TestCase): self.assertEqual(fn('rgb.icns.argb'), (16, 16)) self.assertEqual(fn('256x256.jp2'), (256, 256)) self.assertEqual(fn('18x18.j2k'), (18, 18)) + self.assertEqual(fn('32x32.jpf'), (32, 32)) def test_ext(self): for data, ext in (