From de8b89c82363b5ba4385c611b2ff1fa9d0e5552d Mon Sep 17 00:00:00 2001 From: REDxEYE Date: Mon, 8 Aug 2022 02:21:58 +0300 Subject: [PATCH] Add support for writing LA dds textures --- Tests/images/l.dds | Bin 0 -> 16512 bytes Tests/images/l.png | Bin 0 -> 861 bytes Tests/images/rgb.dds | Bin 0 -> 49280 bytes Tests/images/rgb.png | Bin 0 -> 862 bytes Tests/images/rgba.dds | Bin 0 -> 65664 bytes Tests/images/rgba.png | Bin 0 -> 1302 bytes Tests/test_file_dds.py | 16 ++++ src/PIL/DdsImagePlugin.py | 188 ++++++++++++++++---------------------- 8 files changed, 94 insertions(+), 110 deletions(-) create mode 100644 Tests/images/l.dds create mode 100644 Tests/images/l.png create mode 100644 Tests/images/rgb.dds create mode 100644 Tests/images/rgb.png create mode 100644 Tests/images/rgba.dds create mode 100644 Tests/images/rgba.png diff --git a/Tests/images/l.dds b/Tests/images/l.dds new file mode 100644 index 0000000000000000000000000000000000000000..b82282587ec30665fceafced278f1c59b3402ed1 GIT binary patch literal 16512 zcmeH}-EHGA5QLRH-P>I{2-3o};0}Uxa}TK}#ia!we>Mbmxid@IFa*O(Adcw~@eMx; zf=;LR*MHl#{rikdie4wCtD@J5$rZg$Odd=pyeTl@O@Rr&tAImS3LLsp;L!Id0QjK*;D-W$ zUsiB1AL2y-+`b5a+g}qv@T~yBw*myeRN!Df#TNl|`$YiV{(^u9=Lg$A2l~GQ{ow&5 zpBqU3+`zrxe;YskeE#v{zyA5p51_m@(gG!?cVO_^Sz~$wl>F9wR-n}<1zJu7v^@NP z24p2HAUP}$lKTZm^U(>6`arZRRKVo%R5fre zR}Gw8HE{BUpFaA4SDyNS`QJWZP6L6%3Ic}}1b$%!MXwXnRnhCjXRluPu1rA*)aOis!0Q^t@@IwK>FDp2h5Ah-ZZeIkz?XL+S_*Q`6TLFS!DsV8L z;)?*f{UU&Fe?h>5^Mmbw1o}^b{_p^j&kdx1Zs6YUzl|S%KL7afU;q5)2T)!cX@Qc{ zJ1}_dtTDX-N`7lTE70nb0xc&3S{{Bt1F{kpkQ^2W$^C+(`RD{jeIQ`uK)}f3FOCAW z$Z4Q*r-91D$yC6smsv0<#s|HT48aVmFPj7v|D_{D6`BNV-r-8s>1%bl~ z0>7|=qSuM(s_1oMaz(EblLylYZwgF!Q((gHD&WwS0*9^?IP^UV0DdR{_@MycmlYh$ zhjsv0<#s|HT4 t8aVmFPf`J1DHUKY6=0r08b||aAPuB}G>`_;KpIE`X&?=xfi!T4f&Vw%wVD6` literal 0 HcmV?d00001 diff --git a/Tests/images/l.png b/Tests/images/l.png new file mode 100644 index 0000000000000000000000000000000000000000..9d22a26a446d3dbdfd8f9c931ea466f6c6424e90 GIT binary patch literal 861 zcmeAS@N?(olHy`uVBq!ia0vp^4Is<`Bp9BB+KDqTFspdFIEGZrc^h?ls*<5Vur)JB zqlg-7P(pKyh(}`Z41qR*8_gT%*K1u(y=E67zCX-RIr5$RbYFRmk9~(3o90{0iTo?q zZokIPVc}P`n#F#$cTRI}Z`b*cHdxb-Qkj7%xN9XC&Z5~v}sUc zQF_LyDDi8i`mdSklfSkId~vT6XS8VuNn~8YHGhS2Q^L#RQrG)UyL@3X&}x&GB>;Tib{;+mb; z9E?oS*}aKX$Uxa5;eK2CjmcMXGaH^hIH47DKd5laQEeT@eua-ZO-D9&R()gjOuir% zvW=}bGBx``)z5EfnVi1_vX=2LOsg_|ey?EeT|36sB|Uh!dQU<13AVi4r5g)!U-!1I-}Y|HguFw3`xCAHw!QqqenxjbTuMSUDiOXag;?62vFzD;OU-N3|GNKs$H|s>F&hgWVD=Cdb6BTa*mH8 zSvtelvqpRLzjllcxGA-Tb?VxK$@jOLdwX91dKc*H#c{mLBht%bs(#FW&Mb`zE`Q|r X-moxli&n>DP`>eW^>bP0l+XkK*RXjv literal 0 HcmV?d00001 diff --git a/Tests/images/rgb.dds b/Tests/images/rgb.dds new file mode 100644 index 0000000000000000000000000000000000000000..ad0e630b479f580abc8fb8237c8c460918aa008e GIT binary patch literal 49280 zcmeI1U3L>O3`WBW;E~5Jg@^8d?eM_Pi_>%*WhC2@EX~B>%PA)wjddkm=LhKf`|r0u zm&@h*kIUt6dH($Ktt=dmx03VgEAQv$^T*|K`?*C+4fms>=)z57iIiH`Y?nJwk#ymv z9hgY_?X0~Wb&&Mdlj6N?sq*Oj%C(C7wMmJj+nxw-X~nabj%`}&ikXmf^Hbn;O*nVa zY2E8~Z8wr8o`!E1j=4jJoLox{O(JRf33#bwaEwkkxt2PcMAGyV@KVXh7#%NU{n%Ux zl0pu}KNgF>qJsslw?+ez6m<}OE0g$4rwUwe9S0&Q>LC19ChM7w6u#d62_6o<0n_nW z(g`<7%|U{ayCuB&z3vMnUfZWv>8u^AcH5h% z{CN4t9)W#ymC@=)wcFlAOf=>_iRoUyVGZ5a3nJ%)Ddvt}57 zWZHW&3=IE|viDc1@~`mZ0=XYPx9<1OBCnJ6!2jT|;D7uM8YrB)4gc^z^Bpm^H$%tg z|LFY*{{usV+q3>=^EK~|sSo&Za;l+HUNfS5&viU^vLR*&-GtU zDCr35*=yeDjy;v%6X@X9lzV~P75vL5m}~uk9)1K5^n@?T9bXn$U&)TezCikQ;1P(u zIvli8M1JHE$OUqv@P9wST+>Z@$PqN@L7ze${#{_}oui5O3LJ6IeG1CvG!Dc)O1}RT zv<1@lIgY^8lc={JH1R!0U@LIMZsjSsTj>k0cinvLDfkN@$ zQKA+eq$#E~M~M*=lWrDiJV;ZXWX+o#MogU#X?(*|@lDNGH}lz*5oIAIjcRT6HnobMFWF;Om{@=%1z^mr|`w1Q9(ZAWMtRa zB#oyZ%-@xZz2wt{L~4^EG!8k8UkfEI_-G-K+HDApLk{EDLKzD_S74+x97yA+1Na%v zDU%Ns7&*XCV7DAaMJ26y(ZC4&_fIWQ^{b-#0w?djs_jL?FY`0D)5Xo0ufmXtlL&R32(v(2H%RBm;?X;}AWrKSIt?;hNmC?13r(%HW{ zzcU(c!2k@v01UtY48Q;kzyJ)u01UtY48Q;kzyJ)u01UtY48Q;kzyJ)u01UtY48Q;k azyJ)u01UtY48Q;kzyJ)u01Uvuyn%m65T+~u literal 0 HcmV?d00001 diff --git a/Tests/images/rgb.png b/Tests/images/rgb.png new file mode 100644 index 0000000000000000000000000000000000000000..d0de36d82a9d9b082ec3fd255fc42886698c2649 GIT binary patch literal 862 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1SEZ8zRh7^U{>{XaSW-L^LF;dpfv^ptmZ%e z*B@gKbQCh1OPc~>jD zxZRUqi&<5!Jo6X7W>+5ak4dX8T`%&`UAI&54yRW#he$7YeKOv`@3$Ds|DO2_TNaw< z{N%P+u`et7`|K&KJ_X!d%l6H2%wMkG$^ByibCY62!m7Q;FNuHn^|B#>LqURxqm2P1 z$e}PJtNzp6eGS+B*;$+!4qlnQ`S_`q-jA55G^>NSrIU+aF*P@3_$rorfYoA2)R zdS;vN9sOR@uQ zero={vDNVP)GyDN!^DgVuiWb7lLz|m;oBGLRg8x}Gp-HRJoWv1@$c!C*BO(eGk*GP z`aQq+^Q@15Y7X4_{d?zMEvwIr-tx-F`cJ(RFPh08pz|nAJN5f%%k%6V+Z@hMzWctx zUGSjHGv*~5<8yERRonNBndfWYxi@w4z{pyupMK@`zkhXk`$erl(Y~zZ&(G=HukWXV zJqYnQI1GGB?SCJu=a&TqMsWN&J?-BhtHXfF0AyaE{M?&=?f5=3UaAU@F5Axu^z5AH z%q|x2+PFOA>#JTE3cgko-9Nk_A9MutdWXZKIf$h9}xaC2z;Q@GpCXd~g$#Yfqgp zMU-n#ohUz#{qK>19*$^R5y185`X0V_ZAJN4=8y73>MkJMakIRBDLomEYOaOUE9yvzHuUuzApVu|!a{?#%R z^cLY}RYU$k(4Vqa9Kn7(*ZK>svJOY?$-nVk-UTn~&kB*;i#GX_e_RH@b-Vffe`kLV z_qeoXlzT98AICl`ck;4sPyRp1pX+~I26+ERuKig%j(t&%%s?7LCRyx$sd zA-~8ej|>P?^~iuIKR*M4`~sKAfA(`=0mwH)n3;ha>3n(S8laM&w+1NW7iwae17KQ~ zIe_HtasbFLQeu&TfvH$zU`T#E0|W94l_EJX96pi*Q~CZJ7|Jiz)nj}7*V;S=nv(cW zYxB@gUx9yVZY~4x7isa9&RdSM#alXWso&)N&a)u#)m|InAo0~+8_|cjpEEHufVg5U zUrjq?7FxcVc8I=`KcW5oUt!k7aqmPkK&##Stmes;$rxEIYB zV`%duiC^%L^kKj+LJ`!7Y9oA4cASD)nn zAH(ji(nAiP~Kluk{fa`zzZ#i7nhCufDa{>92e|QE0p8G}8T|JC_tFQV8N4zqdJ{TI!{e)hAz z?*AaX&Vfe;q}kStX1$m6UDg3{0I?Uz-{G@hEaF@BnHjhhPYq_>_qkt<{?s)^zlk!21LebXF31BfrO$iT=fEHW^nZ<2wb0jyDs>BTE3cgpuUQ4(!Xto=ttFPc%p4IbJFlc+bH#Kc_w|RjYdDR z4a&8rPMROewWsbzAL5$y)pe47^tB$2Xj^HHJsi=tH~MOhNgrw}=|^5ec_MXJ^F(lC((kUX z^&{US?5t|?yb*R*HBbF+c1ho)CX##6wql9&^Yp*@`&=u|t$xUVzyF%Q&qtW{)R2F< z3uE>@tR}S$yZ6(4vbM=OKGIJN`8U-`OncT7@(&CE`Sbn{&H(R!-v5yq;PYSjdme|i zA(DO8CV%n|%|PJtze_vZjpK|}`X z>_YL>N(O;p)A_F7yu*krm z{%{6H^jE1xa$q@jBnQU&-8rz-U#TxC3m)T?vf!lO%z}qLHRu2xpaXP(4$uKQKnLgm z9iRhrfDX_BIzR{L03DzMbbt=f0Xjej=l~s{19X56&;dF?2j~DDpaXP(4$uKQKnLgm m9iRhrfDX_BIzR{L03DzMbbt=f0Xjej=l~s{19ag1Iq(x4GQ@5G literal 0 HcmV?d00001 diff --git a/Tests/images/rgba.png b/Tests/images/rgba.png new file mode 100644 index 0000000000000000000000000000000000000000..33b41a547265ca54a15197925df3adb7cd06ba71 GIT binary patch literal 1302 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7xzrV7cq*;uumf=k46P6Yp5?v^b`2 z(6nH+7PS^@e-QB|mEZVdQm(&3V(BwM`sVSKUc( zU2*1$O=ZLVH|eeWR=f`Wd$K-k-^$3fojkC^KXl^iV znEG?gJP_WhX*i1^+@VNJ>G#X{nlbUfK{}(ja&-)ef zYW3=WZw2z=`4kx^HM4o}Nhw%nG(1@V=A29V)V99;r1ZXbjC20myPc@*{^Z#yssv|{OvCWGewv(u1>znvZimUrr{a& zL!uMr#WH6sKd@n685hs%Z2$B*FV&@HDE8r!S55-~T!CRz+Q? z!F;a1-VGkx%Qu-%m^z;?iLL z@>Bek#|-^Qcjh$vhMteP2bnV!QfodlJT|#tq7x-$@{Re$Okk)l`^o+)+dq4Y$${{L zCo6C9FR=dWEc^DQ{EGK-*pVPAz@!{eZcp6 zwv_S`1|e|Rimd-pZ_)5tTzP62P|Hj+woIVG@c1RuKl`n@qhM$ z^Z%-~m+ttdUvaL;hS6%-=A*2$7p7Oa$FGsBT2}t$u=(K%`vWZ>ttWE#8YI^^*Y6Tc z>&bu7p4;-Tj)C#>`YRI+{(H%CepY>*ap4}nLP|~W2L~w+$@4GxFFRb`kde9JKL3J@ ze-nSrdM3Yez3tkIml)z%)t~>ZV|e;f{$+;$e~;r?R?15c+^%C#{%mQh4aC{Oi%b~T zH9WcVjakC~|EX2kwtxA5>3^@^<&)eZ;=lw7;W^FmweHJ*`yV*Zq-yEG_f8h8{eqiZE1bDFjf&7aXchi$yFZ~25_jL7h JS?83{1OPRAV5k59 literal 0 HcmV?d00001 diff --git a/Tests/test_file_dds.py b/Tests/test_file_dds.py index ec8434d75..88032e4ae 100644 --- a/Tests/test_file_dds.py +++ b/Tests/test_file_dds.py @@ -320,3 +320,19 @@ def test_save(mode, test_file, tmp_path): with Image.open(out) as reloaded: assert_image_equal(im, reloaded) + + +@pytest.mark.parametrize( + ("mode", "expected_file", "input_file"), + [ + ("L", "Tests/images/l.png", "Tests/images/l.dds"), + ("LA", "Tests/images/la.png", "Tests/images/la.dds"), + ("RGB", "Tests/images/rgb.png", "Tests/images/rgb.DDS"), + ("RGBA", "Tests/images/rgba.png", "Tests/images/rgba.DDS"), + ], +) +def test_open(mode, expected_file, input_file): + with Image.open(input_file) as im: + assert im.mode == mode + with Image.open(expected_file) as im2: + assert_image_equal(im, im2) diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py index 8175f182e..b1a296f59 100644 --- a/src/PIL/DdsImagePlugin.py +++ b/src/PIL/DdsImagePlugin.py @@ -277,6 +277,7 @@ class DdsImageFile(ImageFile.ImageFile): format = "DDS" format_description = "DirectDraw Surface" + # fmt: off def _open(self): if not _accept(self.fp.read(4)): msg = "not a DDS file" @@ -294,7 +295,6 @@ class DdsImageFile(ImageFile.ImageFile): flags_, height, width = struct.unpack("<3I", header.read(12)) flags = DDSD(flags_) self._size = (width, height) - self.mode = "RGBA" pitch, depth, mipmaps = struct.unpack("<3I", header.read(12)) struct.unpack("<11I", header.read(44)) # reserved @@ -305,21 +305,9 @@ class DdsImageFile(ImageFile.ImageFile): fourcc = D3DFMT(fourcc_) masks = struct.unpack("<4I", header.read(16)) if flags & DDSD.CAPS: - ( - caps1_, - caps2_, - caps3, - caps4, - _, - ) = struct.unpack("<5I", header.read(20)) + (caps1_, caps2_, caps3, caps4, _,) = struct.unpack("<5I", header.read(20)) else: - (caps1_, caps2_, caps3, caps4, _,) = ( - 0, - 0, - 0, - 0, - 0, - ) + (caps1_, caps2_, caps3, caps4, _,) = (0, 0, 0, 0, 0,) caps1 = DDSCAPS(caps1_) caps2 = DDSCAPS2(caps2_) if pfflags & DDPF.LUMINANCE: @@ -333,101 +321,87 @@ class DdsImageFile(ImageFile.ImageFile): elif pfflags & DDPF.RGB: # Texture contains uncompressed RGB data masks = {mask: ["R", "G", "B", "A"][i] for i, mask in enumerate(masks)} + if bitcount == 24: + rawmode = masks[0x00FF0000] + masks[0x0000FF00] + masks[0x000000FF] + self.mode = "RGB" + self.tile = [("raw", (0, 0) + self.size, 0, (rawmode[::-1], 0, 1))] + elif bitcount == 32 and pfflags & DDPF.ALPHAPIXELS: + self.mode = "RGBA" + rawmode = (masks[0xFF000000] + masks[0x00FF0000] + masks[0x0000FF00] + masks[0x000000FF]) + self.tile = [("raw", (0, 0) + self.size, 0, (rawmode[::-1], 0, 1))] + else: + raise OSError(f"Unsupported bitcount {bitcount} for {pfflags} DDS texture") + elif pfflags & DDPF.LUMINANCE: if bitcount == 8: self.mode = "L" - rawmode = "L" - elif bitcount == 24: - self.mode = "RGB" - rawmode = masks[0xFF0000] + masks[0xFF00] + masks[0xFF] - elif bitcount == 32: - self.mode = "RGBA" - rawmode = ( - masks[0xFF000000] + masks[0xFF0000] + masks[0xFF00] + masks[0xFF] - ) + self.tile = [("raw", (0, 0) + self.size, 0, ("L", 0, 1))] + elif bitcount == 16 and pfflags & DDPF.ALPHAPIXELS: + self.mode = "LA" + self.tile = [("raw", (0, 0) + self.size, 0, ("LA", 0, 1))] else: - raise OSError(f"Unsupported bitcount {bitcount} for DDS texture") - - self.tile = [("raw", (0, 0) + self.size, 0, (rawmode[::-1], 0, 1))] + raise OSError(f"Unsupported bitcount {bitcount} for {pfflags} DDS texture") elif pfflags & DDPF.FOURCC: data_start = header_size + 4 if fourcc == D3DFMT.DXT1: + self.mode = "RGBA" self.pixel_format = "DXT1" - tile = Image.Tile( - "bcn", (0, 0) + self.size, data_start, (1, self.pixel_format) - ) + tile = Image.Tile("bcn", (0, 0) + self.size, data_start, (1, self.pixel_format)) elif fourcc == D3DFMT.DXT3: + self.mode = "RGBA" self.pixel_format = "DXT3" - tile = Image.Tile( - "bcn", (0, 0) + self.size, data_start, (2, self.pixel_format) - ) + tile = Image.Tile("bcn", (0, 0) + self.size, data_start, (2, self.pixel_format)) elif fourcc == D3DFMT.DXT5: + self.mode = "RGBA" self.pixel_format = "DXT5" - tile = Image.Tile( - "bcn", (0, 0) + self.size, data_start, (3, self.pixel_format) - ) + tile = Image.Tile("bcn", (0, 0) + self.size, data_start, (3, self.pixel_format)) elif fourcc == D3DFMT.ATI1: - self.pixel_format = "BC4" - tile = Image.Tile( - "bcn", (0, 0) + self.size, data_start, (4, self.pixel_format) - ) self.mode = "L" + self.pixel_format = "BC4" + tile = Image.Tile("bcn", (0, 0) + self.size, data_start, (4, self.pixel_format)) elif fourcc == D3DFMT.BC5S: + self.mode = "RGB" self.pixel_format = "BC5S" - tile = Image.Tile( - "bcn", (0, 0) + self.size, data_start, (5, self.pixel_format) - ) - self.mode = "RGB" + tile = Image.Tile("bcn", (0, 0) + self.size, data_start, (5, self.pixel_format)) elif fourcc == D3DFMT.ATI2: - self.pixel_format = "BC5" - tile = Image.Tile( - "bcn", (0, 0) + self.size, data_start, (5, self.pixel_format) - ) self.mode = "RGB" + self.pixel_format = "BC5" + tile = Image.Tile("bcn", (0, 0) + self.size, data_start, (5, self.pixel_format)) elif fourcc == D3DFMT.DX10: data_start += 20 # ignoring flags which pertain to volume textures and cubemaps (dxgi_format,) = struct.unpack("