From 541bb149a46bf0866c64c036cebf8ec607a8fd5e Mon Sep 17 00:00:00 2001 From: "Artem V. Navrotskiy" Date: Tue, 6 Oct 2020 09:36:06 +0300 Subject: [PATCH 1/6] Add test images --- test/images/lossless_0.jpg | Bin 0 -> 1628 bytes test/images/lossless_1.jpg | Bin 0 -> 1728 bytes test/images/lossless_2.jpg | Bin 0 -> 1726 bytes test/images/lossless_3.jpg | Bin 0 -> 1720 bytes test/images/lossless_4.jpg | Bin 0 -> 1725 bytes test/images/lossless_5.jpg | Bin 0 -> 1674 bytes test/images/lossless_6.jpg | Bin 0 -> 1680 bytes test/images/lossless_7.jpg | Bin 0 -> 1676 bytes test/images/lossless_8.jpg | Bin 0 -> 1679 bytes 9 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 test/images/lossless_0.jpg create mode 100644 test/images/lossless_1.jpg create mode 100644 test/images/lossless_2.jpg create mode 100644 test/images/lossless_3.jpg create mode 100644 test/images/lossless_4.jpg create mode 100644 test/images/lossless_5.jpg create mode 100644 test/images/lossless_6.jpg create mode 100644 test/images/lossless_7.jpg create mode 100644 test/images/lossless_8.jpg diff --git a/test/images/lossless_0.jpg b/test/images/lossless_0.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3c0147c075055ba5d78eb6492c7c7e01dc8b1346 GIT binary patch literal 1628 zcmbV~X;9Ng7{)gj2?5Q#(sV+%`jf~6^eNYIJE5Ks*grH?}C6AW>Ng#R4cCjgHDKY$$w7#_g!Fa#c^ zZ2=?zU?|9~&hTFWgF_zm&=`Fz4mwb60^l$N0**xJTtlZ5p?!eFqfA#@@6~*uK#>R5t&PWCI{J`9p zY;7IaBQA`r1)F-Od08#ebpx|yp||R|CaMsd9&2GBWmxI+Hma}qVpe2aJ;@mTEI?*O zkyTtD{!lh`;00?SYssY6k~F|`R0idYM#ij>ICAZCdc8*9>NVH(m`zd^_r1uyB^(T| z^G$WnrYsEFpF4{M8^CYyv-F@4QKRfcLY{5lyV-r)T$S$0g>P8KUCj9mEFZQU#!KjOO`uE3tGOj#5K$$O41s!1Jj>!*r_$F+c# zdG0LTze*FB!~8(Kk#uu^fymE4TKYKDMCd{^e7<*k4zp8gr1G|XA0Bm)GFpL7*vPG! zwSP*SvAo81DpNNqUa^nf;7=Kk1j#R+Dt0BkP_3VGR&V4Bnrk;8K52pbLjjfdCc;3| z=Ozz7QTr9MGsM`5XsB_3ZWJ&bf<0bUJ&?C1j6}ya6;zx!EJ;^)j7_F_%~%Xw(Snwm zE-kpv@8FB?4lPYHW(0=)7_~WHAd`IVi}9N~6iB9rNq0@{eD1J6hnXBOnYrs^kB};T zoK#0E@>uv?75GT}o<0x+`7{4f3r>?27btU{(DH*JLc*SzICDWRR8NHglfJZcZMfmH^YRiW|lJ z6Zg(oU+zBI70t`L7vsX(8+=NVt~hRGo!!;I7(E`(( z^!ZT;PUQAZRY@l|^J7TMd(~rShIWu@X#wJE6edva@gJTv9so89A8W03S_ zFCTL8{S+#1Wx1Uf%>kEmeb~n(oh21Ep#+ygwIHBZ&74Q)YJquDX_GjfJjO9#Ocy5| z&TuFc?(32b9UrbhJ0D`lZwq3ltNTYg7_8!Eas9@&at5=c`H8gNza*XEUfL+G=icsi R8=G7u58UT{q=ch=^#_a9S70trN{MvYcW^aCOkgBnF7f*>GbgrQJCi3@6FcMB>~TM!); zq(qjo1O%-SL_`LRtRiYc5?MqXB|#*BNKi46<@E&}o$05x=iWE>%)I|O_x%3vAzjEz zVCL@X<_aJP0zK#gNG}N85WhPd08dX~0{|cZ7%~S~G=tCuAZq~6C2BY6-;Lr^dU>JvkiFmvL!H8&NL?V(%#wO-w#wKJF z63NWcjBG)nQmIC!Ry0cr&74A|=$k-Tv<3$o!muI5m}E@(&w<)7|vyN)Ge~ng?6ElCk=^~RuGndQq}jW|Mh@+P&4-K{fF_7THPl-7X+}M zvA&Z1g9|kQ!r*XN9A3`_VdBsSOUA+TZ4J!V`QSqiS}a(8f(Zn%PT4U!iX>ne1MpXE-23e&YZSv}?GRM`d%(jaD^#_vT`m+)*xWPAbDn7%_fv|1eJr|PBI+!&zYKrW zMo>Av@;>z)EiKNuL{~TZG;a4L@rdcGVD;GpLO1#&?cxy^osC%1P_+d60RdjOC61!! z2aP7WJk$~Us=nhs4mEYA8c#T(H%hopA>L0bZmOG(zKV{m&nw%vQ<1D|9T-mXeP`8s z0RfGbZ3ws_ZWYU~^-hm*-bswR2|7!$M5UPV#q{}ACfcTh%XT~U_@UG0EbfWG;nel} zJEV@vyZmZaf%oKJ+Mqk~H|(BZbUw>p5O9bwdWQMYXADlr!QYs%ie5YJFloXZ+M63Q z>+*PPt)s}GoK;{KDOVIzd$(~s_Ruc|ldM>Nxf3aqSEpu65>gnN2)mCyn)ZYoP89{3 z;0f#G;(gbT*q(3S-4-p%xgO)n+Z1v@kvzJ0j%{XJ*RyD44eP#3_yK=?=H8+{BW*dC z#6*DkW%j3j6i(*xT3gO$G>Bv9)}6Y6BfV?sRjfdEtT^I&Tr=wclVg2Q4MR}?SN`t;N%k_+`FS=-%SThwq*Srbr{%<(F& Xlh+8Yw0jH;&r=6^ZrxRsfIR&h=}7CE literal 0 HcmV?d00001 diff --git a/test/images/lossless_2.jpg b/test/images/lossless_2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..50fa2a95a28803f1481a939b3441a076632c25df GIT binary patch literal 1726 zcmbV~dpJ~S9LL`?7c*uUh8elFHiMLXl2FDLrQAz{t!Y&zmr|_Un%rj=m8e|_6-h*D zn`Iuggxs&W9!U_Ti_@NHaA2a|Ckj7{20_lJ0K`4LH z5Zw8(U}tzgSp5Qkq%Xj3fJ7ix7hEWKp|NN*3WdgDFfv#+zCXvZxIfXT=_&@i&I&ns-~{1w_bn4MibMm+svruyZ2bz*xK1UxPEuw zApOwyZeHHUPxzcXKhuHnxD3`{{FnDx37O-aA=e_#vh-Ud^I&azwl;J`1ak>vS^hH0?3b8 zpUM8g1rGs2pioE@W|a#C0Qls6xGU7j9mm|&){yH zijK*s?))m+2eN+$7W==*K7)PX8Uk`i_-GQ5L|_TVrC<~vvs2PKKSExhdfzh3OIpO0 z_-yM*WMJxB7U*Wp4iXKFyTeeH>5-=Br+R`0uRO1xGwB1PepQq4^tGENqTZ`rWVe}$ zIWwCF5<<5}lulXX`*+6Y9^n}+(|bIM6v}vgP4+?~u8CQV<=0})bYSmDNDVjfddRJw zGk0kNs$5Q8-|6Od&mj3s!-Rr8H2Y@Xl_=DlPLzzlYer^L#dvqkm_Iu$OxI7Vk7=}? zPmcBw|3CsXXeSzwpv95PIWPQ--kQEo8ZTV1b@DQ(vwf8*Iyspr1)BsMFVR_MQlq-a zb=g}VWFvmW0e$2&TeGpGBOr&n=wrYVePuz9vvfTDO;DDB_-Q>wq{rOL;b{6KilV6X z=K0pcx7`h<0w+<^s-Y#f!nk?$k7AjGP+IX!W3+`@7Skb~U%--Md6OVvQc{Y4suav- zNP%_my}5>Fbx9v9=j9-iSN%*KE16*KjeKHJLGSLe)tpxCzg8K>&CL#S%87~gujN%G z5n%buS7oV_yjXfR{7w2Y#5k{of^E4CX)p4RvP;=%>^tE$#oB>mj53Nac)C*!_RYXb zN)E_5wmDVMyJ*zowE9XoSmYvs+Ah}RrdV!;aY431VU-?N?0C9!MPAI25L-s(w8L*S zhZzg&W}kD(?*pA}|5Cc|@rRp`S2?6Vk=l-*VJVR3U^`=hRh8Wm?ZenaQoxHHb9^EN zLDgyOoaJvN{QX)an6TFUFihAHp)Wb;x;dbreBOOnIm4AYv%;#Nm+hHM?eKGsD~RkN zoZ36iv@`GT>Y^T}%x|ey+I}osp@_Ai(1g?ZPGgxa(nw*etdCD%%~*N|_?OQ!CjE6- zS0|1!4#TlB^c&kJ4PT4;VZ#vWPY#6QS!e1vC8c-WO79|x5N;>7;5Kx?qz|hM48;xs zmNPfm8>PT%A%NE)Fj5%yd5soUyBmn#TgDhzecZdQK7&FwS`8&@gQKVtN(!Em-WKQMzIWg4xb+2pdltzllwWgI?7<#%RT7#5mD&WR>QTC4Z9 PSYA?ay};?dEgk$DEj!}Y literal 0 HcmV?d00001 diff --git a/test/images/lossless_3.jpg b/test/images/lossless_3.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8a44442fd1c74aaf7a32b60231b4ffc38f6ebf59 GIT binary patch literal 1720 zcmbV}c~DbV6vppM@<<2}6A~6h%P7hwMGz4}Q5F$Ir7UVu3W-Vu5k?e2qQ!`U;zmS3ipsu3RCY*M6oi;S0w#Sy$IkSxw&&jW=FHr4zk7b)k-w4m0OCex zS7!i05O|L)Anygc*GB~f0N~~Zi~#^R07L2kjbxDG4=n)jTn@lSNauU*1sQ&;K>>gw zf6F1H@@pW<$bF!A0YLJ1U>!iCP>PKN2H7wUhA|izkHsqCRPm~+DtHwYHG(=(jX)x( zs1P-YBr-)qLqm0*mbNBETb-goQHVfjw%6Ju|pp*zu5E_lbpcP#s)p5ur||#pVu5WY1mL zB?l=hz!RTxNh9C zb=xoA+jsc*|F$O}aBtAQ@Ce4C$f)SU$Bv&kdFuCs(-)IdE~TcWXRvdxUB8i+Ur>1S z?!A)I`wz+|d z6JB%as@`Q23bZ-0e+PE-f02C$`JzXl3f&Ryp>*pj=WkhcahUPPbD7%SeJ364 zbpvB}gbZtO6&mQkX3G*-uy?t0n>m;*1sG~FIpYR?!2~r5bo>@gPt%M-^L58c8*VstiL!Ma4 zRkz)lJ+GK)bU)%&!<3H1TdI@H(lj`gKpVB&v42b|qzjg5GFyxT77g-f!JWDFLH5te z%aA#V(5W7FnB@bBC-A@!WY6Qs3|B`y=FUmzCUd%+CMGs@298{6L=E|<)oc~Y33 zmKGPy$<7XYnQlQkl+La=&wq-XA1vQ(GjoS$6E)G}1^ZaRk41W3f_-dN~Sk!=JOKqvuHi4D}zJa#9=H4bj zJ$3+QQeuf7*mkb^uZJ=ivR>IXS;F9gtqGOqXak8&GiN>s*m7WT=d~<7Q_py0hRQF` zA{?ksN{Wo;v8BG$3Y&mMAG>M6!)8N0nHHI?rjLYl_meJ}UKx_*^kcdiFo^T-7j*8i zo5|xg`4+}Zn7`mKvX~8pglLXX3oo|2W#BaH$+=;d>Ud<%Ce)F&`4g`n|G}3okOSwi z((#=B{=F6-gBVAmJL!Y%%3U?$^_$HlFnv2q2L^MlAa7Xp8$IxOz2+S2nq*%|QcL=a z#-z__v-%gzbjxWm0o+$6cTKNvaha+Mxf`5ADK|TnBrrXAwN$#Aa6GfbcHhH@8KPTX zRD&ya4V6>;yfH43a9hdmtYp@WBnPQyB)s-rcP2kXi4LsvQ+=03e9=*LOvovy+|})0 zU3Bc?kUuRr=>qehgxA_zu(l?iu3}pAQ^Cm1iaX(S?-RLgiRo3nZ>{NMKjodtSKGwX RIG+vEs&BJ?r0U8${sKL|=;r_c literal 0 HcmV?d00001 diff --git a/test/images/lossless_4.jpg b/test/images/lossless_4.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1cd616a6b90a4e330fc644539811fdfd72181618 GIT binary patch literal 1725 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 literal 0 HcmV?d00001 diff --git a/test/images/lossless_6.jpg b/test/images/lossless_6.jpg new file mode 100644 index 0000000000000000000000000000000000000000..bba3d12a3aaf9272fd4ca8ca5674c231e06fdef8 GIT binary patch literal 1680 zcmbV}dpJ~i7{`BS&Wy_p!!RzPJf+a^tlS$LQ!JGw4AJIUB2l#ETJE#7QHdJDn#774 zrq$4dE+i$QhN5vRcFA2U*Kr$W&qVb+``7Ni=Q-zmpZ9#<-}`>vBkU0V31n?;_Sygl zg1|fE0>WP4Z5@8h7XW*EKmq_r01PAtU_?Pc40)&k%qI_&nB2jb*1S3^wj3fplNtDJ(6aRAv9|M8}*a;575CNbF5KMrC zwLl%25rb?*w3M}gP{@d4;u4Zr9AYSw1tN{(j-Fx=hI=k$5 zbvy9G!NWdBeEp6d^A8CPJ9RodBJ#{HXA=_7oxhNpb|pO{^VclK_56YxH<^V+tUH{$ zW#tw3?mu|)wDwtD{qu&#mex1Cw)T$BuHL@){R4wT!y}`UQ`0lE{JHssMG+SS;8mQ*ZzEDOV{lquVR`Qg*T7&C+{yW&2twP%TGe7zu$%Q!W+ zGW$tv^IU4XK(9B6wVSu_eD^ff@NOvE<`KRnQvF?4vS&;#WA=USlbW6C6@1eJ^~SBU zA#;r!)8pfdA@+_tSZOB3dS)SRA7o4ll9SwyuAZ5}%#s}=FL@P@)IuCO)a2>RleAVwY;A9L zvQCr`M6$hu>>1nq*OQqa_K`W3(z}^P%k+9p&qONX?^H|m8}v)?t925B2RbvlHmW^- zvrT5j{mo6I0__!Br18bORU_Qkf{CFqp5}3PPdvG3@&1rY)_jF$W^NV+#E#b#F13&3 zJYFO}e|3gBm*0^cOwSX$(aJDC-=OJ{=q$?IIWipP5E)xLkVYX#n}?KduZzMiTM-g? zxo^T7+tVxndr)KHvNk-+YIj7MO-aL`sd6p zoh&iW+EaxuTH)y(3U0I>4)f#}MZp==Z>(>{30$h;UTC@}9T=H*`hbrxoE8GBN|e!1 z3Aja;2wATAXbIREKc0u81|5`OX8%dF8&y6h(jV7M!k9G^_JGu#;i?2SJgL25@8yoru zFWFqQFevlj$TZ#0ao2j)mvLB}pF|HIO590QpmlEAU0UQqQl zM>227?8~`A8JMJe%yV8&c3ZIP(KK&x>nl$a0t#IS{;*^&sj#OmPZGxWR4azY34vbe zYF3dw#WD2|c?VM{1ANNTFw)$cx8iE``@>YdXUl4|7c;cES2vc=yDhB4v&Lz{&c6Xp CN6-%d literal 0 HcmV?d00001 diff --git a/test/images/lossless_7.jpg b/test/images/lossless_7.jpg new file mode 100644 index 0000000000000000000000000000000000000000..db54991011d0d3672918b1f8beefeefb201a08ee GIT binary patch literal 1676 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 literal 0 HcmV?d00001 From 153763737f31d0ad013cc508768302a16423a0e1 Mon Sep 17 00:00:00 2001 From: "Artem V. Navrotskiy" Date: Tue, 6 Oct 2020 10:37:23 +0300 Subject: [PATCH 2/6] Add `JpegTran` method for lossless image transformation --- jpeg/decompress.go | 8 + jpeg/jpegtran.go | 122 ++++ jpeg/jpegtran_test.go | 73 ++ jpeg/transupp.c | 1600 +++++++++++++++++++++++++++++++++++++++++ jpeg/transupp.h | 213 ++++++ 5 files changed, 2016 insertions(+) create mode 100644 jpeg/jpegtran.go create mode 100644 jpeg/jpegtran_test.go create mode 100644 jpeg/transupp.c create mode 100644 jpeg/transupp.h diff --git a/jpeg/decompress.go b/jpeg/decompress.go index 5be1754..e5a62f8 100644 --- a/jpeg/decompress.go +++ b/jpeg/decompress.go @@ -190,6 +190,14 @@ func readHeader(dinfo *C.struct_jpeg_decompress_struct) error { return nil } +func readCoefficients(dinfo *C.struct_jpeg_decompress_struct) (*C.jvirt_barray_ptr, error) { + result := C.jpeg_read_coefficients(dinfo) + if result == nil { + return nil, errors.New(jpegErrorMessage(unsafe.Pointer(dinfo))) + } + return result, nil +} + func startDecompress(dinfo *C.struct_jpeg_decompress_struct) error { if C.start_decompress(dinfo) != 0 { return errors.New(jpegErrorMessage(unsafe.Pointer(dinfo))) diff --git a/jpeg/jpegtran.go b/jpeg/jpegtran.go new file mode 100644 index 0000000..e58a2e1 --- /dev/null +++ b/jpeg/jpegtran.go @@ -0,0 +1,122 @@ +package jpeg + +/* +#include +#include +#include "jpeglib.h" +#include "transupp.h" +*/ +import "C" +import ( + "errors" + "fmt" + "io" +) + +type Transform int + +const ( + TransformNone Transform = iota + TransformFlipHorizontal + TransformFlipVertical + TransformTranspose + TransformTransverse + TransformRotate90 + TransformRotate180 + TransformRotate270 +) + +type JpegTranOptions struct { + // Create progressive JPEG file + Progressive bool + // Fail if there is non-transformable edge blocks + Perfect bool + Transform Transform +} + +func NewJpegTranOptions() *JpegTranOptions { + return &JpegTranOptions{ + Progressive: true, + Perfect: true, + } +} + +// +// Based on https://github.com/cloudflare/jpegtran/blob/master/jpegtran.c implementation. +// +func JpegTran(r io.Reader, w io.Writer, options *JpegTranOptions) error { + if options == nil { + options = NewJpegTranOptions() + } + + srcInfo := newDecompress(r) + if srcInfo == nil { + return errors.New("allocation failed") + } + defer destroyDecompress(srcInfo) + + dstInfo, err := newCompress(w) + if err != nil { + return err + } + defer destroyCompress(dstInfo) + + err = readHeader(srcInfo) + if err != nil { + return err + } + + var transformOption C.jpeg_transform_info + if options.Perfect { + transformOption.perfect = 1 + } + + switch options.Transform { + case TransformNone: + transformOption.transform = C.JXFORM_NONE + case TransformFlipHorizontal: + transformOption.transform = C.JXFORM_FLIP_H + case TransformFlipVertical: + transformOption.transform = C.JXFORM_FLIP_V + case TransformTranspose: + transformOption.transform = C.JXFORM_TRANSPOSE + case TransformTransverse: + transformOption.transform = C.JXFORM_TRANSVERSE + case TransformRotate90: + transformOption.transform = C.JXFORM_ROT_90 + case TransformRotate180: + transformOption.transform = C.JXFORM_ROT_180 + case TransformRotate270: + transformOption.transform = C.JXFORM_ROT_270 + default: + return errors.New(fmt.Sprintf("unknown transform: %v", options.Transform)) + } + + //transformOption.transform = C.JXFORM_FLIP_H + if C.jtransform_request_workspace(srcInfo, &transformOption) == 0 { + return errors.New("transformation is not perfect") + } + + srcCoefArrays, err := readCoefficients(srcInfo) + if err != nil { + return err + } + + C.jpeg_copy_critical_parameters(srcInfo, dstInfo) + + if options.Progressive { + C.jpeg_simple_progression(dstInfo) + } + + dstCoefArrays := C.jtransform_adjust_parameters(srcInfo, dstInfo, srcCoefArrays, &transformOption) + + C.jpeg_write_coefficients(dstInfo, dstCoefArrays) + + C.jtransform_execute_transformation(srcInfo, dstInfo, + srcCoefArrays, + &transformOption) + + C.jpeg_finish_compress(dstInfo) + + return nil +} diff --git a/jpeg/jpegtran_test.go b/jpeg/jpegtran_test.go new file mode 100644 index 0000000..08dd2c0 --- /dev/null +++ b/jpeg/jpegtran_test.go @@ -0,0 +1,73 @@ +package jpeg_test + +import ( + "fmt" + "image" + + "github.com/pixiv/go-libjpeg/jpeg" + "github.com/pixiv/go-libjpeg/test/util" + + "bytes" + "path" + "strings" + "testing" +) + +func TestJpegTran(t *testing.T) { + opts := []*jpeg.JpegTranOptions{ + {Progressive: true, Perfect: true}, + {Progressive: true, Perfect: true}, + {Progressive: true, Perfect: true, Transform: jpeg.TransformFlipHorizontal}, + {Progressive: true, Perfect: true, Transform: jpeg.TransformRotate180}, + {Progressive: true, Perfect: true, Transform: jpeg.TransformFlipVertical}, + {Progressive: true, Perfect: true, Transform: jpeg.TransformTranspose}, + {Progressive: true, Perfect: true, Transform: jpeg.TransformRotate90}, + {Progressive: true, Perfect: true, Transform: jpeg.TransformTransverse}, + {Progressive: true, Perfect: true, Transform: jpeg.TransformRotate270}, + } + + expected, err := jpeg.DecodeIntoRGBA(bytes.NewReader(util.ReadFile("lossless_0.jpg")), &jpeg.DecoderOptions{}) + if err != nil { + t.Fatalf("can't decode expected image: %v", err) + } + + for i, opt := range opts { + testJpegTranImage(t, fmt.Sprintf("lossless_%d.jpg", i), expected, opt) + } +} + +func testJpegTranImage(t *testing.T, source string, expected *image.RGBA, opt *jpeg.JpegTranOptions) { + base := strings.TrimSuffix(path.Base(source), path.Ext(source)) + pngName := strings.TrimSuffix(source, path.Ext(source)) + ".png" + t.Run(base, func(t *testing.T) { + src := util.ReadFile(source) + + var buf bytes.Buffer + if err := jpeg.JpegTran(bytes.NewReader(src), &buf, opt); err != nil { + t.Fatalf("can't transform image: %v", err) + } + + actual, err := jpeg.DecodeIntoRGBA(&buf, &jpeg.DecoderOptions{}) + if err != nil { + t.Fatalf("can't decode created image: %v", err) + } + util.WritePNG(actual, pngName) + + ensureSameImage(t, actual, expected) + }) +} + +func ensureSameImage(t *testing.T, a *image.RGBA, b *image.RGBA) { + if a.Rect.Size() != b.Rect.Size() { + t.Fatalf("image has differ size") + } + dy := a.Rect.Dy() + dx := a.Rect.Dx() + for y := 0; y < dy; y++ { + al := a.Pix[y*a.Stride : y*a.Stride+dx*4] + bl := b.Pix[y*b.Stride : y*b.Stride+dx*4] + if !bytes.Equal(al, bl) { + t.Fatalf("image has differ pixels") + } + } +} diff --git a/jpeg/transupp.c b/jpeg/transupp.c new file mode 100644 index 0000000..66e429f --- /dev/null +++ b/jpeg/transupp.c @@ -0,0 +1,1600 @@ +/* + * transupp.c + * + * Copyright (C) 1997-2011, Thomas G. Lane, Guido Vollbeding. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains image transformation routines and other utility code + * used by the jpegtran sample application. These are NOT part of the core + * JPEG library. But we keep these routines separate from jpegtran.c to + * ease the task of maintaining jpegtran-like programs that have other user + * interfaces. + */ + +/* Although this file really shouldn't have access to the library internals, + * it's helpful to let it call jround_up() and jcopy_block_row(). + */ +#define JPEG_INTERNALS + +#include +#include +#include +#include "jpeglib.h" +#include "transupp.h" /* My own external interface */ +#include /* to declare isdigit() */ + +#define SIZEOF(object) ((size_t) sizeof(object)) + +#if TRANSFORMS_SUPPORTED + +/* + * Lossless image transformation routines. These routines work on DCT + * coefficient arrays and thus do not require any lossy decompression + * or recompression of the image. + * Thanks to Guido Vollbeding for the initial design and code of this feature, + * and to Ben Jackson for introducing the cropping feature. + * + * Horizontal flipping is done in-place, using a single top-to-bottom + * pass through the virtual source array. It will thus be much the + * fastest option for images larger than main memory. + * + * The other routines require a set of destination virtual arrays, so they + * need twice as much memory as jpegtran normally does. The destination + * arrays are always written in normal scan order (top to bottom) because + * the virtual array manager expects this. The source arrays will be scanned + * in the corresponding order, which means multiple passes through the source + * arrays for most of the transforms. That could result in much thrashing + * if the image is larger than main memory. + * + * If cropping or trimming is involved, the destination arrays may be smaller + * than the source arrays. Note it is not possible to do horizontal flip + * in-place when a nonzero Y crop offset is specified, since we'd have to move + * data from one block row to another but the virtual array manager doesn't + * guarantee we can touch more than one row at a time. So in that case, + * we have to use a separate destination array. + * + * Some notes about the operating environment of the individual transform + * routines: + * 1. Both the source and destination virtual arrays are allocated from the + * source JPEG object, and therefore should be manipulated by calling the + * source's memory manager. + * 2. The destination's component count should be used. It may be smaller + * than the source's when forcing to grayscale. + * 3. Likewise the destination's sampling factors should be used. When + * forcing to grayscale the destination's sampling factors will be all 1, + * and we may as well take that as the effective iMCU size. + * 4. When "trim" is in effect, the destination's dimensions will be the + * trimmed values but the source's will be untrimmed. + * 5. When "crop" is in effect, the destination's dimensions will be the + * cropped values but the source's will be uncropped. Each transform + * routine is responsible for picking up source data starting at the + * correct X and Y offset for the crop region. (The X and Y offsets + * passed to the transform routines are measured in iMCU blocks of the + * destination.) + * 6. All the routines assume that the source and destination buffers are + * padded out to a full iMCU boundary. This is true, although for the + * source buffer it is an undocumented property of jdcoefct.c. + */ + + +LOCAL(void) +do_crop (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JDIMENSION x_crop_offset, JDIMENSION y_crop_offset, + jvirt_barray_ptr *src_coef_arrays, + jvirt_barray_ptr *dst_coef_arrays) +/* Crop. This is only used when no rotate/flip is requested with the crop. */ +{ + JDIMENSION dst_blk_y, x_crop_blocks, y_crop_blocks; + int ci, offset_y; + JBLOCKARRAY src_buffer, dst_buffer; + jpeg_component_info *compptr; + + /* We simply have to copy the right amount of data (the destination's + * image size) starting at the given X and Y offsets in the source. + */ + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + x_crop_blocks = x_crop_offset * compptr->h_samp_factor; + y_crop_blocks = y_crop_offset * compptr->v_samp_factor; + for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks; + dst_blk_y += compptr->v_samp_factor) { + dst_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, TRUE); + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], + dst_blk_y + y_crop_blocks, + (JDIMENSION) compptr->v_samp_factor, FALSE); + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + jcopy_block_row(src_buffer[offset_y] + x_crop_blocks, + dst_buffer[offset_y], + compptr->width_in_blocks); + } + } + } +} + + +LOCAL(void) +do_flip_h_no_crop (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JDIMENSION x_crop_offset, + jvirt_barray_ptr *src_coef_arrays) +/* Horizontal flip; done in-place, so no separate dest array is required. + * NB: this only works when y_crop_offset is zero. + */ +{ + JDIMENSION MCU_cols, comp_width, blk_x, blk_y, x_crop_blocks; + int ci, k, offset_y; + JBLOCKARRAY buffer; + JCOEFPTR ptr1, ptr2; + JCOEF temp1, temp2; + jpeg_component_info *compptr; + + /* Horizontal mirroring of DCT blocks is accomplished by swapping + * pairs of blocks in-place. Within a DCT block, we perform horizontal + * mirroring by changing the signs of odd-numbered columns. + * Partial iMCUs at the right edge are left untouched. + */ + MCU_cols = srcinfo->output_width / + (dstinfo->max_h_samp_factor * dstinfo->min_DCT_h_scaled_size); + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + comp_width = MCU_cols * compptr->h_samp_factor; + x_crop_blocks = x_crop_offset * compptr->h_samp_factor; + for (blk_y = 0; blk_y < compptr->height_in_blocks; + blk_y += compptr->v_samp_factor) { + buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], blk_y, + (JDIMENSION) compptr->v_samp_factor, TRUE); + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + /* Do the mirroring */ + for (blk_x = 0; blk_x * 2 < comp_width; blk_x++) { + ptr1 = buffer[offset_y][blk_x]; + ptr2 = buffer[offset_y][comp_width - blk_x - 1]; + /* this unrolled loop doesn't need to know which row it's on... */ + for (k = 0; k < DCTSIZE2; k += 2) { + temp1 = *ptr1; /* swap even column */ + temp2 = *ptr2; + *ptr1++ = temp2; + *ptr2++ = temp1; + temp1 = *ptr1; /* swap odd column with sign change */ + temp2 = *ptr2; + *ptr1++ = -temp2; + *ptr2++ = -temp1; + } + } + if (x_crop_blocks > 0) { + /* Now left-justify the portion of the data to be kept. + * We can't use a single jcopy_block_row() call because that routine + * depends on memcpy(), whose behavior is unspecified for overlapping + * source and destination areas. Sigh. + */ + for (blk_x = 0; blk_x < compptr->width_in_blocks; blk_x++) { + jcopy_block_row(buffer[offset_y] + blk_x + x_crop_blocks, + buffer[offset_y] + blk_x, + (JDIMENSION) 1); + } + } + } + } + } +} + + +LOCAL(void) +do_flip_h (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JDIMENSION x_crop_offset, JDIMENSION y_crop_offset, + jvirt_barray_ptr *src_coef_arrays, + jvirt_barray_ptr *dst_coef_arrays) +/* Horizontal flip in general cropping case */ +{ + JDIMENSION MCU_cols, comp_width, dst_blk_x, dst_blk_y; + JDIMENSION x_crop_blocks, y_crop_blocks; + int ci, k, offset_y; + JBLOCKARRAY src_buffer, dst_buffer; + JBLOCKROW src_row_ptr, dst_row_ptr; + JCOEFPTR src_ptr, dst_ptr; + jpeg_component_info *compptr; + + /* Here we must output into a separate array because we can't touch + * different rows of a single virtual array simultaneously. Otherwise, + * this is essentially the same as the routine above. + */ + MCU_cols = srcinfo->output_width / + (dstinfo->max_h_samp_factor * dstinfo->min_DCT_h_scaled_size); + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + comp_width = MCU_cols * compptr->h_samp_factor; + x_crop_blocks = x_crop_offset * compptr->h_samp_factor; + y_crop_blocks = y_crop_offset * compptr->v_samp_factor; + for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks; + dst_blk_y += compptr->v_samp_factor) { + dst_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, TRUE); + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], + dst_blk_y + y_crop_blocks, + (JDIMENSION) compptr->v_samp_factor, FALSE); + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + dst_row_ptr = dst_buffer[offset_y]; + src_row_ptr = src_buffer[offset_y]; + for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks; dst_blk_x++) { + if (x_crop_blocks + dst_blk_x < comp_width) { + /* Do the mirrorable blocks */ + dst_ptr = dst_row_ptr[dst_blk_x]; + src_ptr = src_row_ptr[comp_width - x_crop_blocks - dst_blk_x - 1]; + /* this unrolled loop doesn't need to know which row it's on... */ + for (k = 0; k < DCTSIZE2; k += 2) { + *dst_ptr++ = *src_ptr++; /* copy even column */ + *dst_ptr++ = - *src_ptr++; /* copy odd column with sign change */ + } + } else { + /* Copy last partial block(s) verbatim */ + jcopy_block_row(src_row_ptr + dst_blk_x + x_crop_blocks, + dst_row_ptr + dst_blk_x, + (JDIMENSION) 1); + } + } + } + } + } +} + + +LOCAL(void) +do_flip_v (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JDIMENSION x_crop_offset, JDIMENSION y_crop_offset, + jvirt_barray_ptr *src_coef_arrays, + jvirt_barray_ptr *dst_coef_arrays) +/* Vertical flip */ +{ + JDIMENSION MCU_rows, comp_height, dst_blk_x, dst_blk_y; + JDIMENSION x_crop_blocks, y_crop_blocks; + int ci, i, j, offset_y; + JBLOCKARRAY src_buffer, dst_buffer; + JBLOCKROW src_row_ptr, dst_row_ptr; + JCOEFPTR src_ptr, dst_ptr; + jpeg_component_info *compptr; + + /* We output into a separate array because we can't touch different + * rows of the source virtual array simultaneously. Otherwise, this + * is a pretty straightforward analog of horizontal flip. + * Within a DCT block, vertical mirroring is done by changing the signs + * of odd-numbered rows. + * Partial iMCUs at the bottom edge are copied verbatim. + */ + MCU_rows = srcinfo->output_height / + (dstinfo->max_v_samp_factor * dstinfo->min_DCT_v_scaled_size); + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + comp_height = MCU_rows * compptr->v_samp_factor; + x_crop_blocks = x_crop_offset * compptr->h_samp_factor; + y_crop_blocks = y_crop_offset * compptr->v_samp_factor; + for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks; + dst_blk_y += compptr->v_samp_factor) { + dst_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, TRUE); + if (y_crop_blocks + dst_blk_y < comp_height) { + /* Row is within the mirrorable area. */ + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], + comp_height - y_crop_blocks - dst_blk_y - + (JDIMENSION) compptr->v_samp_factor, + (JDIMENSION) compptr->v_samp_factor, FALSE); + } else { + /* Bottom-edge blocks will be copied verbatim. */ + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], + dst_blk_y + y_crop_blocks, + (JDIMENSION) compptr->v_samp_factor, FALSE); + } + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + if (y_crop_blocks + dst_blk_y < comp_height) { + /* Row is within the mirrorable area. */ + dst_row_ptr = dst_buffer[offset_y]; + src_row_ptr = src_buffer[compptr->v_samp_factor - offset_y - 1]; + src_row_ptr += x_crop_blocks; + for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks; + dst_blk_x++) { + dst_ptr = dst_row_ptr[dst_blk_x]; + src_ptr = src_row_ptr[dst_blk_x]; + for (i = 0; i < DCTSIZE; i += 2) { + /* copy even row */ + for (j = 0; j < DCTSIZE; j++) + *dst_ptr++ = *src_ptr++; + /* copy odd row with sign change */ + for (j = 0; j < DCTSIZE; j++) + *dst_ptr++ = - *src_ptr++; + } + } + } else { + /* Just copy row verbatim. */ + jcopy_block_row(src_buffer[offset_y] + x_crop_blocks, + dst_buffer[offset_y], + compptr->width_in_blocks); + } + } + } + } +} + + +LOCAL(void) +do_transpose (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JDIMENSION x_crop_offset, JDIMENSION y_crop_offset, + jvirt_barray_ptr *src_coef_arrays, + jvirt_barray_ptr *dst_coef_arrays) +/* Transpose source into destination */ +{ + JDIMENSION dst_blk_x, dst_blk_y, x_crop_blocks, y_crop_blocks; + int ci, i, j, offset_x, offset_y; + JBLOCKARRAY src_buffer, dst_buffer; + JCOEFPTR src_ptr, dst_ptr; + jpeg_component_info *compptr; + + /* Transposing pixels within a block just requires transposing the + * DCT coefficients. + * Partial iMCUs at the edges require no special treatment; we simply + * process all the available DCT blocks for every component. + */ + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + x_crop_blocks = x_crop_offset * compptr->h_samp_factor; + y_crop_blocks = y_crop_offset * compptr->v_samp_factor; + for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks; + dst_blk_y += compptr->v_samp_factor) { + dst_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, TRUE); + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks; + dst_blk_x += compptr->h_samp_factor) { + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], + dst_blk_x + x_crop_blocks, + (JDIMENSION) compptr->h_samp_factor, FALSE); + for (offset_x = 0; offset_x < compptr->h_samp_factor; offset_x++) { + dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x]; + src_ptr = src_buffer[offset_x][dst_blk_y + offset_y + y_crop_blocks]; + for (i = 0; i < DCTSIZE; i++) + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + } + } + } + } + } +} + + +LOCAL(void) +do_rot_90 (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JDIMENSION x_crop_offset, JDIMENSION y_crop_offset, + jvirt_barray_ptr *src_coef_arrays, + jvirt_barray_ptr *dst_coef_arrays) +/* 90 degree rotation is equivalent to + * 1. Transposing the image; + * 2. Horizontal mirroring. + * These two steps are merged into a single processing routine. + */ +{ + JDIMENSION MCU_cols, comp_width, dst_blk_x, dst_blk_y; + JDIMENSION x_crop_blocks, y_crop_blocks; + int ci, i, j, offset_x, offset_y; + JBLOCKARRAY src_buffer, dst_buffer; + JCOEFPTR src_ptr, dst_ptr; + jpeg_component_info *compptr; + + /* Because of the horizontal mirror step, we can't process partial iMCUs + * at the (output) right edge properly. They just get transposed and + * not mirrored. + */ + MCU_cols = srcinfo->output_height / + (dstinfo->max_h_samp_factor * dstinfo->min_DCT_h_scaled_size); + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + comp_width = MCU_cols * compptr->h_samp_factor; + x_crop_blocks = x_crop_offset * compptr->h_samp_factor; + y_crop_blocks = y_crop_offset * compptr->v_samp_factor; + for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks; + dst_blk_y += compptr->v_samp_factor) { + dst_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, TRUE); + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks; + dst_blk_x += compptr->h_samp_factor) { + if (x_crop_blocks + dst_blk_x < comp_width) { + /* Block is within the mirrorable area. */ + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], + comp_width - x_crop_blocks - dst_blk_x - + (JDIMENSION) compptr->h_samp_factor, + (JDIMENSION) compptr->h_samp_factor, FALSE); + } else { + /* Edge blocks are transposed but not mirrored. */ + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], + dst_blk_x + x_crop_blocks, + (JDIMENSION) compptr->h_samp_factor, FALSE); + } + for (offset_x = 0; offset_x < compptr->h_samp_factor; offset_x++) { + dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x]; + if (x_crop_blocks + dst_blk_x < comp_width) { + /* Block is within the mirrorable area. */ + src_ptr = src_buffer[compptr->h_samp_factor - offset_x - 1] + [dst_blk_y + offset_y + y_crop_blocks]; + for (i = 0; i < DCTSIZE; i++) { + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + i++; + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j]; + } + } else { + /* Edge blocks are transposed but not mirrored. */ + src_ptr = src_buffer[offset_x] + [dst_blk_y + offset_y + y_crop_blocks]; + for (i = 0; i < DCTSIZE; i++) + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + } + } + } + } + } + } +} + + +LOCAL(void) +do_rot_270 (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JDIMENSION x_crop_offset, JDIMENSION y_crop_offset, + jvirt_barray_ptr *src_coef_arrays, + jvirt_barray_ptr *dst_coef_arrays) +/* 270 degree rotation is equivalent to + * 1. Horizontal mirroring; + * 2. Transposing the image. + * These two steps are merged into a single processing routine. + */ +{ + JDIMENSION MCU_rows, comp_height, dst_blk_x, dst_blk_y; + JDIMENSION x_crop_blocks, y_crop_blocks; + int ci, i, j, offset_x, offset_y; + JBLOCKARRAY src_buffer, dst_buffer; + JCOEFPTR src_ptr, dst_ptr; + jpeg_component_info *compptr; + + /* Because of the horizontal mirror step, we can't process partial iMCUs + * at the (output) bottom edge properly. They just get transposed and + * not mirrored. + */ + MCU_rows = srcinfo->output_width / + (dstinfo->max_v_samp_factor * dstinfo->min_DCT_v_scaled_size); + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + comp_height = MCU_rows * compptr->v_samp_factor; + x_crop_blocks = x_crop_offset * compptr->h_samp_factor; + y_crop_blocks = y_crop_offset * compptr->v_samp_factor; + for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks; + dst_blk_y += compptr->v_samp_factor) { + dst_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, TRUE); + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks; + dst_blk_x += compptr->h_samp_factor) { + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], + dst_blk_x + x_crop_blocks, + (JDIMENSION) compptr->h_samp_factor, FALSE); + for (offset_x = 0; offset_x < compptr->h_samp_factor; offset_x++) { + dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x]; + if (y_crop_blocks + dst_blk_y < comp_height) { + /* Block is within the mirrorable area. */ + src_ptr = src_buffer[offset_x] + [comp_height - y_crop_blocks - dst_blk_y - offset_y - 1]; + for (i = 0; i < DCTSIZE; i++) { + for (j = 0; j < DCTSIZE; j++) { + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + j++; + dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j]; + } + } + } else { + /* Edge blocks are transposed but not mirrored. */ + src_ptr = src_buffer[offset_x] + [dst_blk_y + offset_y + y_crop_blocks]; + for (i = 0; i < DCTSIZE; i++) + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + } + } + } + } + } + } +} + + +LOCAL(void) +do_rot_180 (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JDIMENSION x_crop_offset, JDIMENSION y_crop_offset, + jvirt_barray_ptr *src_coef_arrays, + jvirt_barray_ptr *dst_coef_arrays) +/* 180 degree rotation is equivalent to + * 1. Vertical mirroring; + * 2. Horizontal mirroring. + * These two steps are merged into a single processing routine. + */ +{ + JDIMENSION MCU_cols, MCU_rows, comp_width, comp_height, dst_blk_x, dst_blk_y; + JDIMENSION x_crop_blocks, y_crop_blocks; + int ci, i, j, offset_y; + JBLOCKARRAY src_buffer, dst_buffer; + JBLOCKROW src_row_ptr, dst_row_ptr; + JCOEFPTR src_ptr, dst_ptr; + jpeg_component_info *compptr; + + MCU_cols = srcinfo->output_width / + (dstinfo->max_h_samp_factor * dstinfo->min_DCT_h_scaled_size); + MCU_rows = srcinfo->output_height / + (dstinfo->max_v_samp_factor * dstinfo->min_DCT_v_scaled_size); + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + comp_width = MCU_cols * compptr->h_samp_factor; + comp_height = MCU_rows * compptr->v_samp_factor; + x_crop_blocks = x_crop_offset * compptr->h_samp_factor; + y_crop_blocks = y_crop_offset * compptr->v_samp_factor; + for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks; + dst_blk_y += compptr->v_samp_factor) { + dst_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, TRUE); + if (y_crop_blocks + dst_blk_y < comp_height) { + /* Row is within the vertically mirrorable area. */ + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], + comp_height - y_crop_blocks - dst_blk_y - + (JDIMENSION) compptr->v_samp_factor, + (JDIMENSION) compptr->v_samp_factor, FALSE); + } else { + /* Bottom-edge rows are only mirrored horizontally. */ + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], + dst_blk_y + y_crop_blocks, + (JDIMENSION) compptr->v_samp_factor, FALSE); + } + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + dst_row_ptr = dst_buffer[offset_y]; + if (y_crop_blocks + dst_blk_y < comp_height) { + /* Row is within the mirrorable area. */ + src_row_ptr = src_buffer[compptr->v_samp_factor - offset_y - 1]; + for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks; dst_blk_x++) { + dst_ptr = dst_row_ptr[dst_blk_x]; + if (x_crop_blocks + dst_blk_x < comp_width) { + /* Process the blocks that can be mirrored both ways. */ + src_ptr = src_row_ptr[comp_width - x_crop_blocks - dst_blk_x - 1]; + for (i = 0; i < DCTSIZE; i += 2) { + /* For even row, negate every odd column. */ + for (j = 0; j < DCTSIZE; j += 2) { + *dst_ptr++ = *src_ptr++; + *dst_ptr++ = - *src_ptr++; + } + /* For odd row, negate every even column. */ + for (j = 0; j < DCTSIZE; j += 2) { + *dst_ptr++ = - *src_ptr++; + *dst_ptr++ = *src_ptr++; + } + } + } else { + /* Any remaining right-edge blocks are only mirrored vertically. */ + src_ptr = src_row_ptr[x_crop_blocks + dst_blk_x]; + for (i = 0; i < DCTSIZE; i += 2) { + for (j = 0; j < DCTSIZE; j++) + *dst_ptr++ = *src_ptr++; + for (j = 0; j < DCTSIZE; j++) + *dst_ptr++ = - *src_ptr++; + } + } + } + } else { + /* Remaining rows are just mirrored horizontally. */ + src_row_ptr = src_buffer[offset_y]; + for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks; dst_blk_x++) { + if (x_crop_blocks + dst_blk_x < comp_width) { + /* Process the blocks that can be mirrored. */ + dst_ptr = dst_row_ptr[dst_blk_x]; + src_ptr = src_row_ptr[comp_width - x_crop_blocks - dst_blk_x - 1]; + for (i = 0; i < DCTSIZE2; i += 2) { + *dst_ptr++ = *src_ptr++; + *dst_ptr++ = - *src_ptr++; + } + } else { + /* Any remaining right-edge blocks are only copied. */ + jcopy_block_row(src_row_ptr + dst_blk_x + x_crop_blocks, + dst_row_ptr + dst_blk_x, + (JDIMENSION) 1); + } + } + } + } + } + } +} + + +LOCAL(void) +do_transverse (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JDIMENSION x_crop_offset, JDIMENSION y_crop_offset, + jvirt_barray_ptr *src_coef_arrays, + jvirt_barray_ptr *dst_coef_arrays) +/* Transverse transpose is equivalent to + * 1. 180 degree rotation; + * 2. Transposition; + * or + * 1. Horizontal mirroring; + * 2. Transposition; + * 3. Horizontal mirroring. + * These steps are merged into a single processing routine. + */ +{ + JDIMENSION MCU_cols, MCU_rows, comp_width, comp_height, dst_blk_x, dst_blk_y; + JDIMENSION x_crop_blocks, y_crop_blocks; + int ci, i, j, offset_x, offset_y; + JBLOCKARRAY src_buffer, dst_buffer; + JCOEFPTR src_ptr, dst_ptr; + jpeg_component_info *compptr; + + MCU_cols = srcinfo->output_height / + (dstinfo->max_h_samp_factor * dstinfo->min_DCT_h_scaled_size); + MCU_rows = srcinfo->output_width / + (dstinfo->max_v_samp_factor * dstinfo->min_DCT_v_scaled_size); + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + comp_width = MCU_cols * compptr->h_samp_factor; + comp_height = MCU_rows * compptr->v_samp_factor; + x_crop_blocks = x_crop_offset * compptr->h_samp_factor; + y_crop_blocks = y_crop_offset * compptr->v_samp_factor; + for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks; + dst_blk_y += compptr->v_samp_factor) { + dst_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, TRUE); + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks; + dst_blk_x += compptr->h_samp_factor) { + if (x_crop_blocks + dst_blk_x < comp_width) { + /* Block is within the mirrorable area. */ + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], + comp_width - x_crop_blocks - dst_blk_x - + (JDIMENSION) compptr->h_samp_factor, + (JDIMENSION) compptr->h_samp_factor, FALSE); + } else { + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], + dst_blk_x + x_crop_blocks, + (JDIMENSION) compptr->h_samp_factor, FALSE); + } + for (offset_x = 0; offset_x < compptr->h_samp_factor; offset_x++) { + dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x]; + if (y_crop_blocks + dst_blk_y < comp_height) { + if (x_crop_blocks + dst_blk_x < comp_width) { + /* Block is within the mirrorable area. */ + src_ptr = src_buffer[compptr->h_samp_factor - offset_x - 1] + [comp_height - y_crop_blocks - dst_blk_y - offset_y - 1]; + for (i = 0; i < DCTSIZE; i++) { + for (j = 0; j < DCTSIZE; j++) { + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + j++; + dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j]; + } + i++; + for (j = 0; j < DCTSIZE; j++) { + dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j]; + j++; + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + } + } + } else { + /* Right-edge blocks are mirrored in y only */ + src_ptr = src_buffer[offset_x] + [comp_height - y_crop_blocks - dst_blk_y - offset_y - 1]; + for (i = 0; i < DCTSIZE; i++) { + for (j = 0; j < DCTSIZE; j++) { + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + j++; + dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j]; + } + } + } + } else { + if (x_crop_blocks + dst_blk_x < comp_width) { + /* Bottom-edge blocks are mirrored in x only */ + src_ptr = src_buffer[compptr->h_samp_factor - offset_x - 1] + [dst_blk_y + offset_y + y_crop_blocks]; + for (i = 0; i < DCTSIZE; i++) { + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + i++; + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j]; + } + } else { + /* At lower right corner, just transpose, no mirroring */ + src_ptr = src_buffer[offset_x] + [dst_blk_y + offset_y + y_crop_blocks]; + for (i = 0; i < DCTSIZE; i++) + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + } + } + } + } + } + } + } +} + + +/* Parse an unsigned integer: subroutine for jtransform_parse_crop_spec. + * Returns TRUE if valid integer found, FALSE if not. + * *strptr is advanced over the digit string, and *result is set to its value. + */ + +LOCAL(boolean) +jt_read_integer (const char ** strptr, JDIMENSION * result) +{ + const char * ptr = *strptr; + JDIMENSION val = 0; + + for (; isdigit(*ptr); ptr++) { + val = val * 10 + (JDIMENSION) (*ptr - '0'); + } + *result = val; + if (ptr == *strptr) + return FALSE; /* oops, no digits */ + *strptr = ptr; + return TRUE; +} + + +/* Parse a crop specification (written in X11 geometry style). + * The routine returns TRUE if the spec string is valid, FALSE if not. + * + * The crop spec string should have the format + * [f]x[f]{+-}{+-} + * where width, height, xoffset, and yoffset are unsigned integers. + * Each of the elements can be omitted to indicate a default value. + * (A weakness of this style is that it is not possible to omit xoffset + * while specifying yoffset, since they look alike.) + * + * This code is loosely based on XParseGeometry from the X11 distribution. + */ + +GLOBAL(boolean) +jtransform_parse_crop_spec (jpeg_transform_info *info, const char *spec) +{ + info->crop = FALSE; + info->crop_width_set = JCROP_UNSET; + info->crop_height_set = JCROP_UNSET; + info->crop_xoffset_set = JCROP_UNSET; + info->crop_yoffset_set = JCROP_UNSET; + + if (isdigit(*spec)) { + /* fetch width */ + if (! jt_read_integer(&spec, &info->crop_width)) + return FALSE; + if (*spec == 'f' || *spec == 'F') { + spec++; + info->crop_width_set = JCROP_FORCE; + } else + info->crop_width_set = JCROP_POS; + } + if (*spec == 'x' || *spec == 'X') { + /* fetch height */ + spec++; + if (! jt_read_integer(&spec, &info->crop_height)) + return FALSE; + if (*spec == 'f' || *spec == 'F') { + spec++; + info->crop_height_set = JCROP_FORCE; + } else + info->crop_height_set = JCROP_POS; + } + if (*spec == '+' || *spec == '-') { + /* fetch xoffset */ + info->crop_xoffset_set = (*spec == '-') ? JCROP_NEG : JCROP_POS; + spec++; + if (! jt_read_integer(&spec, &info->crop_xoffset)) + return FALSE; + } + if (*spec == '+' || *spec == '-') { + /* fetch yoffset */ + info->crop_yoffset_set = (*spec == '-') ? JCROP_NEG : JCROP_POS; + spec++; + if (! jt_read_integer(&spec, &info->crop_yoffset)) + return FALSE; + } + /* We had better have gotten to the end of the string. */ + if (*spec != '\0') + return FALSE; + info->crop = TRUE; + return TRUE; +} + + +/* Trim off any partial iMCUs on the indicated destination edge */ + +LOCAL(void) +trim_right_edge (jpeg_transform_info *info, JDIMENSION full_width) +{ + JDIMENSION MCU_cols; + + MCU_cols = info->output_width / info->iMCU_sample_width; + if (MCU_cols > 0 && info->x_crop_offset + MCU_cols == + full_width / info->iMCU_sample_width) + info->output_width = MCU_cols * info->iMCU_sample_width; +} + +LOCAL(void) +trim_bottom_edge (jpeg_transform_info *info, JDIMENSION full_height) +{ + JDIMENSION MCU_rows; + + MCU_rows = info->output_height / info->iMCU_sample_height; + if (MCU_rows > 0 && info->y_crop_offset + MCU_rows == + full_height / info->iMCU_sample_height) + info->output_height = MCU_rows * info->iMCU_sample_height; +} + + +/* Request any required workspace. + * + * This routine figures out the size that the output image will be + * (which implies that all the transform parameters must be set before + * it is called). + * + * We allocate the workspace virtual arrays from the source decompression + * object, so that all the arrays (both the original data and the workspace) + * will be taken into account while making memory management decisions. + * Hence, this routine must be called after jpeg_read_header (which reads + * the image dimensions) and before jpeg_read_coefficients (which realizes + * the source's virtual arrays). + * + * This function returns FALSE right away if -perfect is given + * and transformation is not perfect. Otherwise returns TRUE. + */ + +GLOBAL(boolean) +jtransform_request_workspace (j_decompress_ptr srcinfo, + jpeg_transform_info *info) +{ + jvirt_barray_ptr *coef_arrays; + boolean need_workspace, transpose_it; + jpeg_component_info *compptr; + JDIMENSION xoffset, yoffset; + JDIMENSION width_in_iMCUs, height_in_iMCUs; + JDIMENSION width_in_blocks, height_in_blocks; + int ci, h_samp_factor, v_samp_factor; + + /* Determine number of components in output image */ + if (info->force_grayscale && + srcinfo->jpeg_color_space == JCS_YCbCr && + srcinfo->num_components == 3) + /* We'll only process the first component */ + info->num_components = 1; + else + /* Process all the components */ + info->num_components = srcinfo->num_components; + + /* Compute output image dimensions and related values. */ + jpeg_core_output_dimensions(srcinfo); + + /* Return right away if -perfect is given and transformation is not perfect. + */ + if (info->perfect) { + if (info->num_components == 1) { + if (!jtransform_perfect_transform(srcinfo->output_width, + srcinfo->output_height, + srcinfo->min_DCT_h_scaled_size, + srcinfo->min_DCT_v_scaled_size, + info->transform)) + return FALSE; + } else { + if (!jtransform_perfect_transform(srcinfo->output_width, + srcinfo->output_height, + srcinfo->max_h_samp_factor * srcinfo->min_DCT_h_scaled_size, + srcinfo->max_v_samp_factor * srcinfo->min_DCT_v_scaled_size, + info->transform)) + return FALSE; + } + } + + /* If there is only one output component, force the iMCU size to be 1; + * else use the source iMCU size. (This allows us to do the right thing + * when reducing color to grayscale, and also provides a handy way of + * cleaning up "funny" grayscale images whose sampling factors are not 1x1.) + */ + switch (info->transform) { + case JXFORM_TRANSPOSE: + case JXFORM_TRANSVERSE: + case JXFORM_ROT_90: + case JXFORM_ROT_270: + info->output_width = srcinfo->output_height; + info->output_height = srcinfo->output_width; + if (info->num_components == 1) { + info->iMCU_sample_width = srcinfo->min_DCT_v_scaled_size; + info->iMCU_sample_height = srcinfo->min_DCT_h_scaled_size; + } else { + info->iMCU_sample_width = + srcinfo->max_v_samp_factor * srcinfo->min_DCT_v_scaled_size; + info->iMCU_sample_height = + srcinfo->max_h_samp_factor * srcinfo->min_DCT_h_scaled_size; + } + break; + default: + info->output_width = srcinfo->output_width; + info->output_height = srcinfo->output_height; + if (info->num_components == 1) { + info->iMCU_sample_width = srcinfo->min_DCT_h_scaled_size; + info->iMCU_sample_height = srcinfo->min_DCT_v_scaled_size; + } else { + info->iMCU_sample_width = + srcinfo->max_h_samp_factor * srcinfo->min_DCT_h_scaled_size; + info->iMCU_sample_height = + srcinfo->max_v_samp_factor * srcinfo->min_DCT_v_scaled_size; + } + break; + } + + /* If cropping has been requested, compute the crop area's position and + * dimensions, ensuring that its upper left corner falls at an iMCU boundary. + */ + if (info->crop) { + /* Insert default values for unset crop parameters */ + if (info->crop_xoffset_set == JCROP_UNSET) + info->crop_xoffset = 0; /* default to +0 */ + if (info->crop_yoffset_set == JCROP_UNSET) + info->crop_yoffset = 0; /* default to +0 */ + if (info->crop_xoffset >= info->output_width || + info->crop_yoffset >= info->output_height) + ERREXIT(srcinfo, JERR_BAD_CROP_SPEC); + if (info->crop_width_set == JCROP_UNSET) + info->crop_width = info->output_width - info->crop_xoffset; + if (info->crop_height_set == JCROP_UNSET) + info->crop_height = info->output_height - info->crop_yoffset; + /* Ensure parameters are valid */ + if (info->crop_width <= 0 || info->crop_width > info->output_width || + info->crop_height <= 0 || info->crop_height > info->output_height || + info->crop_xoffset > info->output_width - info->crop_width || + info->crop_yoffset > info->output_height - info->crop_height) + ERREXIT(srcinfo, JERR_BAD_CROP_SPEC); + /* Convert negative crop offsets into regular offsets */ + if (info->crop_xoffset_set == JCROP_NEG) + xoffset = info->output_width - info->crop_width - info->crop_xoffset; + else + xoffset = info->crop_xoffset; + if (info->crop_yoffset_set == JCROP_NEG) + yoffset = info->output_height - info->crop_height - info->crop_yoffset; + else + yoffset = info->crop_yoffset; + /* Now adjust so that upper left corner falls at an iMCU boundary */ + if (info->crop_width_set == JCROP_FORCE) + info->output_width = info->crop_width; + else + info->output_width = + info->crop_width + (xoffset % info->iMCU_sample_width); + if (info->crop_height_set == JCROP_FORCE) + info->output_height = info->crop_height; + else + info->output_height = + info->crop_height + (yoffset % info->iMCU_sample_height); + /* Save x/y offsets measured in iMCUs */ + info->x_crop_offset = xoffset / info->iMCU_sample_width; + info->y_crop_offset = yoffset / info->iMCU_sample_height; + } else { + info->x_crop_offset = 0; + info->y_crop_offset = 0; + } + + /* Figure out whether we need workspace arrays, + * and if so whether they are transposed relative to the source. + */ + need_workspace = FALSE; + transpose_it = FALSE; + switch (info->transform) { + case JXFORM_NONE: + if (info->x_crop_offset != 0 || info->y_crop_offset != 0) + need_workspace = TRUE; + /* No workspace needed if neither cropping nor transforming */ + break; + case JXFORM_FLIP_H: + if (info->trim) + trim_right_edge(info, srcinfo->output_width); + if (info->y_crop_offset != 0) + need_workspace = TRUE; + /* do_flip_h_no_crop doesn't need a workspace array */ + break; + case JXFORM_FLIP_V: + if (info->trim) + trim_bottom_edge(info, srcinfo->output_height); + /* Need workspace arrays having same dimensions as source image. */ + need_workspace = TRUE; + break; + case JXFORM_TRANSPOSE: + /* transpose does NOT have to trim anything */ + /* Need workspace arrays having transposed dimensions. */ + need_workspace = TRUE; + transpose_it = TRUE; + break; + case JXFORM_TRANSVERSE: + if (info->trim) { + trim_right_edge(info, srcinfo->output_height); + trim_bottom_edge(info, srcinfo->output_width); + } + /* Need workspace arrays having transposed dimensions. */ + need_workspace = TRUE; + transpose_it = TRUE; + break; + case JXFORM_ROT_90: + if (info->trim) + trim_right_edge(info, srcinfo->output_height); + /* Need workspace arrays having transposed dimensions. */ + need_workspace = TRUE; + transpose_it = TRUE; + break; + case JXFORM_ROT_180: + if (info->trim) { + trim_right_edge(info, srcinfo->output_width); + trim_bottom_edge(info, srcinfo->output_height); + } + /* Need workspace arrays having same dimensions as source image. */ + need_workspace = TRUE; + break; + case JXFORM_ROT_270: + if (info->trim) + trim_bottom_edge(info, srcinfo->output_width); + /* Need workspace arrays having transposed dimensions. */ + need_workspace = TRUE; + transpose_it = TRUE; + break; + } + + /* Allocate workspace if needed. + * Note that we allocate arrays padded out to the next iMCU boundary, + * so that transform routines need not worry about missing edge blocks. + */ + if (need_workspace) { + coef_arrays = (jvirt_barray_ptr *) + (*srcinfo->mem->alloc_small) ((j_common_ptr) srcinfo, JPOOL_IMAGE, + SIZEOF(jvirt_barray_ptr) * info->num_components); + width_in_iMCUs = (JDIMENSION) + jdiv_round_up((long) info->output_width, + (long) info->iMCU_sample_width); + height_in_iMCUs = (JDIMENSION) + jdiv_round_up((long) info->output_height, + (long) info->iMCU_sample_height); + for (ci = 0; ci < info->num_components; ci++) { + compptr = srcinfo->comp_info + ci; + if (info->num_components == 1) { + /* we're going to force samp factors to 1x1 in this case */ + h_samp_factor = v_samp_factor = 1; + } else if (transpose_it) { + h_samp_factor = compptr->v_samp_factor; + v_samp_factor = compptr->h_samp_factor; + } else { + h_samp_factor = compptr->h_samp_factor; + v_samp_factor = compptr->v_samp_factor; + } + width_in_blocks = width_in_iMCUs * h_samp_factor; + height_in_blocks = height_in_iMCUs * v_samp_factor; + coef_arrays[ci] = (*srcinfo->mem->request_virt_barray) + ((j_common_ptr) srcinfo, JPOOL_IMAGE, FALSE, + width_in_blocks, height_in_blocks, (JDIMENSION) v_samp_factor); + } + info->workspace_coef_arrays = coef_arrays; + } else + info->workspace_coef_arrays = NULL; + + return TRUE; +} + + +/* Transpose destination image parameters */ + +LOCAL(void) +transpose_critical_parameters (j_compress_ptr dstinfo) +{ + int tblno, i, j, ci, itemp; + jpeg_component_info *compptr; + JQUANT_TBL *qtblptr; + JDIMENSION jtemp; + UINT16 qtemp; + + /* Transpose image dimensions */ + jtemp = dstinfo->image_width; + dstinfo->image_width = dstinfo->image_height; + dstinfo->image_height = jtemp; + itemp = dstinfo->min_DCT_h_scaled_size; + dstinfo->min_DCT_h_scaled_size = dstinfo->min_DCT_v_scaled_size; + dstinfo->min_DCT_v_scaled_size = itemp; + + /* Transpose sampling factors */ + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + itemp = compptr->h_samp_factor; + compptr->h_samp_factor = compptr->v_samp_factor; + compptr->v_samp_factor = itemp; + } + + /* Transpose quantization tables */ + for (tblno = 0; tblno < NUM_QUANT_TBLS; tblno++) { + qtblptr = dstinfo->quant_tbl_ptrs[tblno]; + if (qtblptr != NULL) { + for (i = 0; i < DCTSIZE; i++) { + for (j = 0; j < i; j++) { + qtemp = qtblptr->quantval[i*DCTSIZE+j]; + qtblptr->quantval[i*DCTSIZE+j] = qtblptr->quantval[j*DCTSIZE+i]; + qtblptr->quantval[j*DCTSIZE+i] = qtemp; + } + } + } + } +} + + +/* Adjust Exif image parameters. + * + * We try to adjust the Tags ExifImageWidth and ExifImageHeight if possible. + */ + +LOCAL(void) +adjust_exif_parameters (JOCTET FAR * data, unsigned int length, + JDIMENSION new_width, JDIMENSION new_height) +{ + boolean is_motorola; /* Flag for byte order */ + unsigned int number_of_tags, tagnum; + unsigned int firstoffset, offset; + JDIMENSION new_value; + + if (length < 12) return; /* Length of an IFD entry */ + + /* Discover byte order */ + if (GETJOCTET(data[0]) == 0x49 && GETJOCTET(data[1]) == 0x49) + is_motorola = FALSE; + else if (GETJOCTET(data[0]) == 0x4D && GETJOCTET(data[1]) == 0x4D) + is_motorola = TRUE; + else + return; + + /* Check Tag Mark */ + if (is_motorola) { + if (GETJOCTET(data[2]) != 0) return; + if (GETJOCTET(data[3]) != 0x2A) return; + } else { + if (GETJOCTET(data[3]) != 0) return; + if (GETJOCTET(data[2]) != 0x2A) return; + } + + /* Get first IFD offset (offset to IFD0) */ + if (is_motorola) { + if (GETJOCTET(data[4]) != 0) return; + if (GETJOCTET(data[5]) != 0) return; + firstoffset = GETJOCTET(data[6]); + firstoffset <<= 8; + firstoffset += GETJOCTET(data[7]); + } else { + if (GETJOCTET(data[7]) != 0) return; + if (GETJOCTET(data[6]) != 0) return; + firstoffset = GETJOCTET(data[5]); + firstoffset <<= 8; + firstoffset += GETJOCTET(data[4]); + } + if (firstoffset > length - 2) return; /* check end of data segment */ + + /* Get the number of directory entries contained in this IFD */ + if (is_motorola) { + number_of_tags = GETJOCTET(data[firstoffset]); + number_of_tags <<= 8; + number_of_tags += GETJOCTET(data[firstoffset+1]); + } else { + number_of_tags = GETJOCTET(data[firstoffset+1]); + number_of_tags <<= 8; + number_of_tags += GETJOCTET(data[firstoffset]); + } + if (number_of_tags == 0) return; + firstoffset += 2; + + /* Search for ExifSubIFD offset Tag in IFD0 */ + for (;;) { + if (firstoffset > length - 12) return; /* check end of data segment */ + /* Get Tag number */ + if (is_motorola) { + tagnum = GETJOCTET(data[firstoffset]); + tagnum <<= 8; + tagnum += GETJOCTET(data[firstoffset+1]); + } else { + tagnum = GETJOCTET(data[firstoffset+1]); + tagnum <<= 8; + tagnum += GETJOCTET(data[firstoffset]); + } + if (tagnum == 0x8769) break; /* found ExifSubIFD offset Tag */ + if (--number_of_tags == 0) return; + firstoffset += 12; + } + + /* Get the ExifSubIFD offset */ + if (is_motorola) { + if (GETJOCTET(data[firstoffset+8]) != 0) return; + if (GETJOCTET(data[firstoffset+9]) != 0) return; + offset = GETJOCTET(data[firstoffset+10]); + offset <<= 8; + offset += GETJOCTET(data[firstoffset+11]); + } else { + if (GETJOCTET(data[firstoffset+11]) != 0) return; + if (GETJOCTET(data[firstoffset+10]) != 0) return; + offset = GETJOCTET(data[firstoffset+9]); + offset <<= 8; + offset += GETJOCTET(data[firstoffset+8]); + } + if (offset > length - 2) return; /* check end of data segment */ + + /* Get the number of directory entries contained in this SubIFD */ + if (is_motorola) { + number_of_tags = GETJOCTET(data[offset]); + number_of_tags <<= 8; + number_of_tags += GETJOCTET(data[offset+1]); + } else { + number_of_tags = GETJOCTET(data[offset+1]); + number_of_tags <<= 8; + number_of_tags += GETJOCTET(data[offset]); + } + if (number_of_tags < 2) return; + offset += 2; + + /* Search for ExifImageWidth and ExifImageHeight Tags in this SubIFD */ + do { + if (offset > length - 12) return; /* check end of data segment */ + /* Get Tag number */ + if (is_motorola) { + tagnum = GETJOCTET(data[offset]); + tagnum <<= 8; + tagnum += GETJOCTET(data[offset+1]); + } else { + tagnum = GETJOCTET(data[offset+1]); + tagnum <<= 8; + tagnum += GETJOCTET(data[offset]); + } + if (tagnum == 0xA002 || tagnum == 0xA003) { + if (tagnum == 0xA002) + new_value = new_width; /* ExifImageWidth Tag */ + else + new_value = new_height; /* ExifImageHeight Tag */ + if (is_motorola) { + data[offset+2] = 0; /* Format = unsigned long (4 octets) */ + data[offset+3] = 4; + data[offset+4] = 0; /* Number Of Components = 1 */ + data[offset+5] = 0; + data[offset+6] = 0; + data[offset+7] = 1; + data[offset+8] = 0; + data[offset+9] = 0; + data[offset+10] = (JOCTET)((new_value >> 8) & 0xFF); + data[offset+11] = (JOCTET)(new_value & 0xFF); + } else { + data[offset+2] = 4; /* Format = unsigned long (4 octets) */ + data[offset+3] = 0; + data[offset+4] = 1; /* Number Of Components = 1 */ + data[offset+5] = 0; + data[offset+6] = 0; + data[offset+7] = 0; + data[offset+8] = (JOCTET)(new_value & 0xFF); + data[offset+9] = (JOCTET)((new_value >> 8) & 0xFF); + data[offset+10] = 0; + data[offset+11] = 0; + } + } + offset += 12; + } while (--number_of_tags); +} + + +/* Adjust output image parameters as needed. + * + * This must be called after jpeg_copy_critical_parameters() + * and before jpeg_write_coefficients(). + * + * The return value is the set of virtual coefficient arrays to be written + * (either the ones allocated by jtransform_request_workspace, or the + * original source data arrays). The caller will need to pass this value + * to jpeg_write_coefficients(). + */ + +GLOBAL(jvirt_barray_ptr *) +jtransform_adjust_parameters (j_decompress_ptr srcinfo, + j_compress_ptr dstinfo, + jvirt_barray_ptr *src_coef_arrays, + jpeg_transform_info *info) +{ + /* If force-to-grayscale is requested, adjust destination parameters */ + if (info->force_grayscale) { + /* First, ensure we have YCbCr or grayscale data, and that the source's + * Y channel is full resolution. (No reasonable person would make Y + * be less than full resolution, so actually coping with that case + * isn't worth extra code space. But we check it to avoid crashing.) + */ + if (((dstinfo->jpeg_color_space == JCS_YCbCr && + dstinfo->num_components == 3) || + (dstinfo->jpeg_color_space == JCS_GRAYSCALE && + dstinfo->num_components == 1)) && + srcinfo->comp_info[0].h_samp_factor == srcinfo->max_h_samp_factor && + srcinfo->comp_info[0].v_samp_factor == srcinfo->max_v_samp_factor) { + /* We use jpeg_set_colorspace to make sure subsidiary settings get fixed + * properly. Among other things, it sets the target h_samp_factor & + * v_samp_factor to 1, which typically won't match the source. + * We have to preserve the source's quantization table number, however. + */ + int sv_quant_tbl_no = dstinfo->comp_info[0].quant_tbl_no; + jpeg_set_colorspace(dstinfo, JCS_GRAYSCALE); + dstinfo->comp_info[0].quant_tbl_no = sv_quant_tbl_no; + } else { + /* Sorry, can't do it */ + ERREXIT(dstinfo, JERR_CONVERSION_NOTIMPL); + } + } else if (info->num_components == 1) { + /* For a single-component source, we force the destination sampling factors + * to 1x1, with or without force_grayscale. This is useful because some + * decoders choke on grayscale images with other sampling factors. + */ + dstinfo->comp_info[0].h_samp_factor = 1; + dstinfo->comp_info[0].v_samp_factor = 1; + } + + /* Correct the destination's image dimensions as necessary + * for rotate/flip, resize, and crop operations. + */ + dstinfo->jpeg_width = info->output_width; + dstinfo->jpeg_height = info->output_height; + + /* Transpose destination image parameters */ + switch (info->transform) { + case JXFORM_TRANSPOSE: + case JXFORM_TRANSVERSE: + case JXFORM_ROT_90: + case JXFORM_ROT_270: + transpose_critical_parameters(dstinfo); + break; + default: + break; + } + + /* Adjust Exif properties */ + if (srcinfo->marker_list != NULL && + srcinfo->marker_list->marker == JPEG_APP0+1 && + srcinfo->marker_list->data_length >= 6 && + GETJOCTET(srcinfo->marker_list->data[0]) == 0x45 && + GETJOCTET(srcinfo->marker_list->data[1]) == 0x78 && + GETJOCTET(srcinfo->marker_list->data[2]) == 0x69 && + GETJOCTET(srcinfo->marker_list->data[3]) == 0x66 && + GETJOCTET(srcinfo->marker_list->data[4]) == 0 && + GETJOCTET(srcinfo->marker_list->data[5]) == 0) { + /* Suppress output of JFIF marker */ + dstinfo->write_JFIF_header = FALSE; + /* Adjust Exif image parameters */ + if (dstinfo->jpeg_width != srcinfo->image_width || + dstinfo->jpeg_height != srcinfo->image_height) + /* Align data segment to start of TIFF structure for parsing */ + adjust_exif_parameters(srcinfo->marker_list->data + 6, + srcinfo->marker_list->data_length - 6, + dstinfo->jpeg_width, dstinfo->jpeg_height); + } + + /* Return the appropriate output data set */ + if (info->workspace_coef_arrays != NULL) + return info->workspace_coef_arrays; + return src_coef_arrays; +} + + +/* Execute the actual transformation, if any. + * + * This must be called *after* jpeg_write_coefficients, because it depends + * on jpeg_write_coefficients to have computed subsidiary values such as + * the per-component width and height fields in the destination object. + * + * Note that some transformations will modify the source data arrays! + */ + +GLOBAL(void) +jtransform_execute_transform (j_decompress_ptr srcinfo, + j_compress_ptr dstinfo, + jvirt_barray_ptr *src_coef_arrays, + jpeg_transform_info *info) +{ + jvirt_barray_ptr *dst_coef_arrays = info->workspace_coef_arrays; + + /* Note: conditions tested here should match those in switch statement + * in jtransform_request_workspace() + */ + switch (info->transform) { + case JXFORM_NONE: + if (info->x_crop_offset != 0 || info->y_crop_offset != 0) + do_crop(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset, + src_coef_arrays, dst_coef_arrays); + break; + case JXFORM_FLIP_H: + if (info->y_crop_offset != 0) + do_flip_h(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset, + src_coef_arrays, dst_coef_arrays); + else + do_flip_h_no_crop(srcinfo, dstinfo, info->x_crop_offset, + src_coef_arrays); + break; + case JXFORM_FLIP_V: + do_flip_v(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset, + src_coef_arrays, dst_coef_arrays); + break; + case JXFORM_TRANSPOSE: + do_transpose(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset, + src_coef_arrays, dst_coef_arrays); + break; + case JXFORM_TRANSVERSE: + do_transverse(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset, + src_coef_arrays, dst_coef_arrays); + break; + case JXFORM_ROT_90: + do_rot_90(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset, + src_coef_arrays, dst_coef_arrays); + break; + case JXFORM_ROT_180: + do_rot_180(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset, + src_coef_arrays, dst_coef_arrays); + break; + case JXFORM_ROT_270: + do_rot_270(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset, + src_coef_arrays, dst_coef_arrays); + break; + } +} + +/* jtransform_perfect_transform + * + * Determine whether lossless transformation is perfectly + * possible for a specified image and transformation. + * + * Inputs: + * image_width, image_height: source image dimensions. + * MCU_width, MCU_height: pixel dimensions of MCU. + * transform: transformation identifier. + * Parameter sources from initialized jpeg_struct + * (after reading source header): + * image_width = cinfo.image_width + * image_height = cinfo.image_height + * MCU_width = cinfo.max_h_samp_factor * cinfo.block_size + * MCU_height = cinfo.max_v_samp_factor * cinfo.block_size + * Result: + * TRUE = perfect transformation possible + * FALSE = perfect transformation not possible + * (may use custom action then) + */ + +GLOBAL(boolean) +jtransform_perfect_transform(JDIMENSION image_width, JDIMENSION image_height, + int MCU_width, int MCU_height, + JXFORM_CODE transform) +{ + boolean result = TRUE; /* initialize TRUE */ + + switch (transform) { + case JXFORM_FLIP_H: + case JXFORM_ROT_270: + if (image_width % (JDIMENSION) MCU_width) + result = FALSE; + break; + case JXFORM_FLIP_V: + case JXFORM_ROT_90: + if (image_height % (JDIMENSION) MCU_height) + result = FALSE; + break; + case JXFORM_TRANSVERSE: + case JXFORM_ROT_180: + if (image_width % (JDIMENSION) MCU_width) + result = FALSE; + if (image_height % (JDIMENSION) MCU_height) + result = FALSE; + break; + default: + break; + } + + return result; +} + +#endif /* TRANSFORMS_SUPPORTED */ + + +/* Setup decompression object to save desired markers in memory. + * This must be called before jpeg_read_header() to have the desired effect. + */ + +GLOBAL(void) +jcopy_markers_setup (j_decompress_ptr srcinfo, JCOPY_OPTION option) +{ +#ifdef SAVE_MARKERS_SUPPORTED + int m; + + /* Save comments except under NONE option */ + if (option != JCOPYOPT_NONE) { + jpeg_save_markers(srcinfo, JPEG_COM, 0xFFFF); + } + /* Save all types of APPn markers iff ALL option */ + if (option == JCOPYOPT_ALL) { + for (m = 0; m < 16; m++) + jpeg_save_markers(srcinfo, JPEG_APP0 + m, 0xFFFF); + } +#endif /* SAVE_MARKERS_SUPPORTED */ +} + +/* Copy markers saved in the given source object to the destination object. + * This should be called just after jpeg_start_compress() or + * jpeg_write_coefficients(). + * Note that those routines will have written the SOI, and also the + * JFIF APP0 or Adobe APP14 markers if selected. + */ + +GLOBAL(void) +jcopy_markers_execute (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JCOPY_OPTION option) +{ + jpeg_saved_marker_ptr marker; + + /* In the current implementation, we don't actually need to examine the + * option flag here; we just copy everything that got saved. + * But to avoid confusion, we do not output JFIF and Adobe APP14 markers + * if the encoder library already wrote one. + */ + for (marker = srcinfo->marker_list; marker != NULL; marker = marker->next) { + if (dstinfo->write_JFIF_header && + marker->marker == JPEG_APP0 && + marker->data_length >= 5 && + GETJOCTET(marker->data[0]) == 0x4A && + GETJOCTET(marker->data[1]) == 0x46 && + GETJOCTET(marker->data[2]) == 0x49 && + GETJOCTET(marker->data[3]) == 0x46 && + GETJOCTET(marker->data[4]) == 0) + continue; /* reject duplicate JFIF */ + if (dstinfo->write_Adobe_marker && + marker->marker == JPEG_APP0+14 && + marker->data_length >= 5 && + GETJOCTET(marker->data[0]) == 0x41 && + GETJOCTET(marker->data[1]) == 0x64 && + GETJOCTET(marker->data[2]) == 0x6F && + GETJOCTET(marker->data[3]) == 0x62 && + GETJOCTET(marker->data[4]) == 0x65) + continue; /* reject duplicate Adobe */ +#ifdef NEED_FAR_POINTERS + /* We could use jpeg_write_marker if the data weren't FAR... */ + { + unsigned int i; + jpeg_write_m_header(dstinfo, marker->marker, marker->data_length); + for (i = 0; i < marker->data_length; i++) + jpeg_write_m_byte(dstinfo, marker->data[i]); + } +#else + jpeg_write_marker(dstinfo, marker->marker, + marker->data, marker->data_length); +#endif + } +} diff --git a/jpeg/transupp.h b/jpeg/transupp.h new file mode 100644 index 0000000..9aa0af3 --- /dev/null +++ b/jpeg/transupp.h @@ -0,0 +1,213 @@ +/* + * transupp.h + * + * Copyright (C) 1997-2011, Thomas G. Lane, Guido Vollbeding. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains declarations for image transformation routines and + * other utility code used by the jpegtran sample application. These are + * NOT part of the core JPEG library. But we keep these routines separate + * from jpegtran.c to ease the task of maintaining jpegtran-like programs + * that have other user interfaces. + * + * NOTE: all the routines declared here have very specific requirements + * about when they are to be executed during the reading and writing of the + * source and destination files. See the comments in transupp.c, or see + * jpegtran.c for an example of correct usage. + */ + +/* If you happen not to want the image transform support, disable it here */ +#ifndef TRANSFORMS_SUPPORTED +#define TRANSFORMS_SUPPORTED 1 /* 0 disables transform code */ +#endif + +/* + * Although rotating and flipping data expressed as DCT coefficients is not + * hard, there is an asymmetry in the JPEG format specification for images + * whose dimensions aren't multiples of the iMCU size. The right and bottom + * image edges are padded out to the next iMCU boundary with junk data; but + * no padding is possible at the top and left edges. If we were to flip + * the whole image including the pad data, then pad garbage would become + * visible at the top and/or left, and real pixels would disappear into the + * pad margins --- perhaps permanently, since encoders & decoders may not + * bother to preserve DCT blocks that appear to be completely outside the + * nominal image area. So, we have to exclude any partial iMCUs from the + * basic transformation. + * + * Transpose is the only transformation that can handle partial iMCUs at the + * right and bottom edges completely cleanly. flip_h can flip partial iMCUs + * at the bottom, but leaves any partial iMCUs at the right edge untouched. + * Similarly flip_v leaves any partial iMCUs at the bottom edge untouched. + * The other transforms are defined as combinations of these basic transforms + * and process edge blocks in a way that preserves the equivalence. + * + * The "trim" option causes untransformable partial iMCUs to be dropped; + * this is not strictly lossless, but it usually gives the best-looking + * result for odd-size images. Note that when this option is active, + * the expected mathematical equivalences between the transforms may not hold. + * (For example, -rot 270 -trim trims only the bottom edge, but -rot 90 -trim + * followed by -rot 180 -trim trims both edges.) + * + * We also offer a lossless-crop option, which discards data outside a given + * image region but losslessly preserves what is inside. Like the rotate and + * flip transforms, lossless crop is restricted by the JPEG format: the upper + * left corner of the selected region must fall on an iMCU boundary. If this + * does not hold for the given crop parameters, we silently move the upper left + * corner up and/or left to make it so, simultaneously increasing the region + * dimensions to keep the lower right crop corner unchanged. (Thus, the + * output image covers at least the requested region, but may cover more.) + * The adjustment of the region dimensions may be optionally disabled. + * + * We also provide a lossless-resize option, which is kind of a lossless-crop + * operation in the DCT coefficient block domain - it discards higher-order + * coefficients and losslessly preserves lower-order coefficients of a + * sub-block. + * + * Rotate/flip transform, resize, and crop can be requested together in a + * single invocation. The crop is applied last --- that is, the crop region + * is specified in terms of the destination image after transform/resize. + * + * We also offer a "force to grayscale" option, which simply discards the + * chrominance channels of a YCbCr image. This is lossless in the sense that + * the luminance channel is preserved exactly. It's not the same kind of + * thing as the rotate/flip transformations, but it's convenient to handle it + * as part of this package, mainly because the transformation routines have to + * be aware of the option to know how many components to work on. + */ + + +/* Short forms of external names for systems with brain-damaged linkers. */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jtransform_parse_crop_spec jTrParCrop +#define jtransform_request_workspace jTrRequest +#define jtransform_adjust_parameters jTrAdjust +#define jtransform_execute_transform jTrExec +#define jtransform_perfect_transform jTrPerfect +#define jcopy_markers_setup jCMrkSetup +#define jcopy_markers_execute jCMrkExec +#endif /* NEED_SHORT_EXTERNAL_NAMES */ + + +/* + * Codes for supported types of image transformations. + */ + +typedef enum { + JXFORM_NONE, /* no transformation */ + JXFORM_FLIP_H, /* horizontal flip */ + JXFORM_FLIP_V, /* vertical flip */ + JXFORM_TRANSPOSE, /* transpose across UL-to-LR axis */ + JXFORM_TRANSVERSE, /* transpose across UR-to-LL axis */ + JXFORM_ROT_90, /* 90-degree clockwise rotation */ + JXFORM_ROT_180, /* 180-degree rotation */ + JXFORM_ROT_270 /* 270-degree clockwise (or 90 ccw) */ +} JXFORM_CODE; + +/* + * Codes for crop parameters, which can individually be unspecified, + * positive or negative for xoffset or yoffset, + * positive or forced for width or height. + */ + +typedef enum { + JCROP_UNSET, + JCROP_POS, + JCROP_NEG, + JCROP_FORCE +} JCROP_CODE; + +/* + * Transform parameters struct. + * NB: application must not change any elements of this struct after + * calling jtransform_request_workspace. + */ + +typedef struct { + /* Options: set by caller */ + JXFORM_CODE transform; /* image transform operator */ + boolean perfect; /* if TRUE, fail if partial MCUs are requested */ + boolean trim; /* if TRUE, trim partial MCUs as needed */ + boolean force_grayscale; /* if TRUE, convert color image to grayscale */ + boolean crop; /* if TRUE, crop source image */ + + /* Crop parameters: application need not set these unless crop is TRUE. + * These can be filled in by jtransform_parse_crop_spec(). + */ + JDIMENSION crop_width; /* Width of selected region */ + JCROP_CODE crop_width_set; /* (forced disables adjustment) */ + JDIMENSION crop_height; /* Height of selected region */ + JCROP_CODE crop_height_set; /* (forced disables adjustment) */ + JDIMENSION crop_xoffset; /* X offset of selected region */ + JCROP_CODE crop_xoffset_set; /* (negative measures from right edge) */ + JDIMENSION crop_yoffset; /* Y offset of selected region */ + JCROP_CODE crop_yoffset_set; /* (negative measures from bottom edge) */ + + /* Internal workspace: caller should not touch these */ + int num_components; /* # of components in workspace */ + jvirt_barray_ptr * workspace_coef_arrays; /* workspace for transformations */ + JDIMENSION output_width; /* cropped destination dimensions */ + JDIMENSION output_height; + JDIMENSION x_crop_offset; /* destination crop offsets measured in iMCUs */ + JDIMENSION y_crop_offset; + int iMCU_sample_width; /* destination iMCU size */ + int iMCU_sample_height; +} jpeg_transform_info; + + +#if TRANSFORMS_SUPPORTED + +/* Parse a crop specification (written in X11 geometry style) */ +EXTERN(boolean) jtransform_parse_crop_spec + JPP((jpeg_transform_info *info, const char *spec)); +/* Request any required workspace */ +EXTERN(boolean) jtransform_request_workspace + JPP((j_decompress_ptr srcinfo, jpeg_transform_info *info)); +/* Adjust output image parameters */ +EXTERN(jvirt_barray_ptr *) jtransform_adjust_parameters + JPP((j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + jvirt_barray_ptr *src_coef_arrays, + jpeg_transform_info *info)); +/* Execute the actual transformation, if any */ +EXTERN(void) jtransform_execute_transform + JPP((j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + jvirt_barray_ptr *src_coef_arrays, + jpeg_transform_info *info)); +/* Determine whether lossless transformation is perfectly + * possible for a specified image and transformation. + */ +EXTERN(boolean) jtransform_perfect_transform + JPP((JDIMENSION image_width, JDIMENSION image_height, + int MCU_width, int MCU_height, + JXFORM_CODE transform)); + +/* jtransform_execute_transform used to be called + * jtransform_execute_transformation, but some compilers complain about + * routine names that long. This macro is here to avoid breaking any + * old source code that uses the original name... + */ +#define jtransform_execute_transformation jtransform_execute_transform + +#endif /* TRANSFORMS_SUPPORTED */ + + +/* + * Support for copying optional markers from source to destination file. + */ + +typedef enum { + JCOPYOPT_NONE, /* copy no optional markers */ + JCOPYOPT_COMMENTS, /* copy only comment (COM) markers */ + JCOPYOPT_ALL /* copy all optional markers */ +} JCOPY_OPTION; + +#define JCOPYOPT_DEFAULT JCOPYOPT_COMMENTS /* recommended default */ + +/* Setup decompression object to save desired markers in memory */ +EXTERN(void) jcopy_markers_setup + JPP((j_decompress_ptr srcinfo, JCOPY_OPTION option)); +/* Copy markers saved in the given source object to the destination object */ +EXTERN(void) jcopy_markers_execute + JPP((j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JCOPY_OPTION option)); From 7e7148619a26b2e09f0393906a0f09f8f8faeb55 Mon Sep 17 00:00:00 2001 From: "Artem V. Navrotskiy" Date: Tue, 6 Oct 2020 12:50:11 +0300 Subject: [PATCH 3/6] Add autorotate to `JpegTran` --- jpeg/exif.c | 166 ++++++++++++++++++++++++++++++++++++++++++ jpeg/exif.h | 8 ++ jpeg/jpegtran.go | 25 ++++++- jpeg/jpegtran_test.go | 20 ++--- 4 files changed, 204 insertions(+), 15 deletions(-) create mode 100644 jpeg/exif.c create mode 100644 jpeg/exif.h diff --git a/jpeg/exif.c b/jpeg/exif.c new file mode 100644 index 0000000..2c63e90 --- /dev/null +++ b/jpeg/exif.c @@ -0,0 +1,166 @@ +#include +#include +#include +#include +#include "jpeglib.h" +#include "exif.h" + +#define EXIF_JPEG_MARKER JPEG_APP0+1 +#define EXIF_IDENT_STRING "Exif\000\000" + +#define G_LITTLE_ENDIAN 1234 +#define G_BIG_ENDIAN 4321 + +typedef unsigned int uint; +typedef unsigned short ushort; + +const char leth[] = {0x49, 0x49, 0x2a, 0x00}; // Little endian TIFF header +const char beth[] = {0x4d, 0x4d, 0x00, 0x2a}; // Big endian TIFF header + +LOCAL( unsigned short ) +de_get16( void * ptr, uint endian ) { + unsigned char *bytes = (unsigned char *)(ptr); + if ( endian == G_BIG_ENDIAN ) + { + return bytes[1] + (((uint)bytes[0]) << 8); + } + return bytes[0] + (((uint)bytes[1]) << 8); +} + +LOCAL( unsigned int ) +de_get32( void * ptr, uint endian ) { + unsigned char *bytes = (unsigned char *)(ptr); + if ( endian == G_BIG_ENDIAN ) + { + return bytes[3] + (((uint)bytes[2]) << 8) + (((uint)bytes[1]) << 16) + (((uint)bytes[0]) << 24); + } + return bytes[0] + (((uint)bytes[1]) << 8) + (((uint)bytes[2]) << 16) + (((uint)bytes[3]) << 24); +} + +int jpegtran_get_orientation (j_decompress_ptr cinfo) +{ + /* This function looks through the meta data in the libjpeg decompress structure to + determine if an EXIF Orientation tag is present and if so return its value (1-8). + If no EXIF Orientation tag is found 0 (zero) is returned. */ + + uint i; /* index into working buffer */ + uint orient_tag_id; /* endianed version of orientation tag ID */ + uint ret; /* Return value */ + uint offset; /* de-endianed offset in various situations */ + uint tags; /* number of tags in current ifd */ + uint tag; /* de-endianed tag */ + uint type; /* de-endianed type of tag used as index into types[] */ + uint count; /* de-endianed count of elements in a tag */ + uint tiff = 0; /* offset to active tiff header */ + uint endian = 0; /* detected endian of data */ + + jpeg_saved_marker_ptr exif_marker; /* Location of the Exif APP1 marker */ + jpeg_saved_marker_ptr cmarker; /* Location to check for Exif APP1 marker */ + + /* check for Exif marker (also called the APP1 marker) */ + exif_marker = NULL; + cmarker = cinfo->marker_list; + while (cmarker) { + if (cmarker->marker == EXIF_JPEG_MARKER) { + /* The Exif APP1 marker should contain a unique + identification string ("Exif\0\0"). Check for it. */ + if (!memcmp (cmarker->data, EXIF_IDENT_STRING, 6)) { + exif_marker = cmarker; + } + } + cmarker = cmarker->next; + } + /* Did we find the Exif APP1 marker? */ + if (exif_marker == NULL) + return 0; + /* Do we have enough data? */ + if (exif_marker->data_length < 32) + return 0; + + /* Check for TIFF header and catch endianess */ + i = 0; + + /* Just skip data until TIFF header - it should be within 16 bytes from marker start. + Normal structure relative to APP1 marker - + 0x0000: APP1 marker entry = 2 bytes + 0x0002: APP1 length entry = 2 bytes + 0x0004: Exif Identifier entry = 6 bytes + 0x000A: Start of TIFF header (Byte order entry) - 4 bytes + - This is what we look for, to determine endianess. + 0x000E: 0th IFD offset pointer - 4 bytes + + exif_marker->data points to the first data after the APP1 marker + and length entries, which is the exif identification string. + The TIFF header should thus normally be found at i=6, below, + and the pointer to IFD0 will be at 6+4 = 10. + */ + + while (i < 16) { + + /* Little endian TIFF header */ + if (memcmp (&exif_marker->data[i], leth, 4) == 0){ + endian = G_LITTLE_ENDIAN; + } + + /* Big endian TIFF header */ + else if (memcmp (&exif_marker->data[i], beth, 4) == 0){ + endian = G_BIG_ENDIAN; + } + + /* Keep looking through buffer */ + else { + i++; + continue; + } + /* We have found either big or little endian TIFF header */ + tiff = i; + break; + } + + /* So did we find a TIFF header or did we just hit end of buffer? */ + if (tiff == 0) + return 0; + + /* Read out the offset pointer to IFD0 */ + offset = de_get32(&exif_marker->data[i] + 4, endian); + i = i + offset; + + /* Check that we still are within the buffer and can read the tag count */ + if ((i + 2) > exif_marker->data_length) + return 0; + + /* Find out how many tags we have in IFD0. As per the TIFF spec, the first + two bytes of the IFD contain a count of the number of tags. */ + tags = de_get16(&exif_marker->data[i], endian); + i = i + 2; + + /* Check that we still have enough data for all tags to check. The tags + are listed in consecutive 12-byte blocks. The tag ID, type, size, and + a pointer to the actual value, are packed into these 12 byte entries. */ + if ((i + tags * 12) > exif_marker->data_length) + return 0; + + /* Check through IFD0 for tags of interest */ + while (tags--){ + tag = de_get16(&exif_marker->data[i], endian); + type = de_get16(&exif_marker->data[i + 2], endian); + count = de_get32(&exif_marker->data[i + 4], endian); + + /* Is this the orientation tag? */ + if (tag == 0x112){ + + /* Check that type and count fields are OK. The orientation field + will consist of a single (count=1) 2-byte integer (type=3). */ + if (type != 3 || count != 1) return 0; + + /* Return the orientation value. Within the 12-byte block, the + pointer to the actual data is at offset 8. */ + ret = de_get16(&exif_marker->data[i + 8], endian); + return ret <= 8 ? ret : 0; + } + /* move the pointer to the next 12-byte tag field. */ + i = i + 12; + } + + return 0; /* No EXIF Orientation tag found */ +} diff --git a/jpeg/exif.h b/jpeg/exif.h new file mode 100644 index 0000000..01094f5 --- /dev/null +++ b/jpeg/exif.h @@ -0,0 +1,8 @@ +#pragma once + +#include +#include +#include +#include "jpeglib.h" + +int jpegtran_get_orientation (j_decompress_ptr cinfo); diff --git a/jpeg/jpegtran.go b/jpeg/jpegtran.go index e58a2e1..6df4c15 100644 --- a/jpeg/jpegtran.go +++ b/jpeg/jpegtran.go @@ -5,6 +5,7 @@ package jpeg #include #include "jpeglib.h" #include "transupp.h" +#include "exif.h" */ import "C" import ( @@ -16,7 +17,8 @@ import ( type Transform int const ( - TransformNone Transform = iota + TransformAuto Transform = iota + TransformNone TransformFlipHorizontal TransformFlipVertical TransformTranspose @@ -61,6 +63,8 @@ func JpegTran(r io.Reader, w io.Writer, options *JpegTranOptions) error { } defer destroyCompress(dstInfo) + C.jcopy_markers_setup(srcInfo, C.JCOPYOPT_ALL); + err = readHeader(srcInfo) if err != nil { return err @@ -72,6 +76,25 @@ func JpegTran(r io.Reader, w io.Writer, options *JpegTranOptions) error { } switch options.Transform { + case TransformAuto: + switch C.jpegtran_get_orientation(srcInfo) { + case 2: + transformOption.transform = C.JXFORM_FLIP_H + case 3: + transformOption.transform = C.JXFORM_ROT_180 + case 4: + transformOption.transform = C.JXFORM_FLIP_V + case 5: + transformOption.transform = C.JXFORM_TRANSPOSE + case 6: + transformOption.transform = C.JXFORM_ROT_90 + case 7: + transformOption.transform = C.JXFORM_TRANSVERSE + case 8: + transformOption.transform = C.JXFORM_ROT_270 + default: + transformOption.transform = C.JXFORM_NONE + } case TransformNone: transformOption.transform = C.JXFORM_NONE case TransformFlipHorizontal: diff --git a/jpeg/jpegtran_test.go b/jpeg/jpegtran_test.go index 08dd2c0..3517858 100644 --- a/jpeg/jpegtran_test.go +++ b/jpeg/jpegtran_test.go @@ -14,25 +14,17 @@ import ( ) func TestJpegTran(t *testing.T) { - opts := []*jpeg.JpegTranOptions{ - {Progressive: true, Perfect: true}, - {Progressive: true, Perfect: true}, - {Progressive: true, Perfect: true, Transform: jpeg.TransformFlipHorizontal}, - {Progressive: true, Perfect: true, Transform: jpeg.TransformRotate180}, - {Progressive: true, Perfect: true, Transform: jpeg.TransformFlipVertical}, - {Progressive: true, Perfect: true, Transform: jpeg.TransformTranspose}, - {Progressive: true, Perfect: true, Transform: jpeg.TransformRotate90}, - {Progressive: true, Perfect: true, Transform: jpeg.TransformTransverse}, - {Progressive: true, Perfect: true, Transform: jpeg.TransformRotate270}, - } - expected, err := jpeg.DecodeIntoRGBA(bytes.NewReader(util.ReadFile("lossless_0.jpg")), &jpeg.DecoderOptions{}) if err != nil { t.Fatalf("can't decode expected image: %v", err) } - for i, opt := range opts { - testJpegTranImage(t, fmt.Sprintf("lossless_%d.jpg", i), expected, opt) + for i := 0; i <= 8; i++ { + testJpegTranImage(t, fmt.Sprintf("lossless_%d.jpg", i), expected, &jpeg.JpegTranOptions{ + Progressive: true, + Perfect: true, + Transform: jpeg.TransformAuto, + }) } } From a5dce81fe590afd633b7830353b80d330c8a2b73 Mon Sep 17 00:00:00 2001 From: "Artem V. Navrotskiy" Date: Tue, 6 Oct 2020 13:09:42 +0300 Subject: [PATCH 4/6] More errors handled --- jpeg/compress.go | 5 +++++ jpeg/jpegtran.go | 12 ++++++++---- jpeg/jpegtran_test.go | 5 +++++ 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/jpeg/compress.go b/jpeg/compress.go index 9fefacd..8c9a357 100644 --- a/jpeg/compress.go +++ b/jpeg/compress.go @@ -194,6 +194,11 @@ func finishCompress(cinfo *C.struct_jpeg_compress_struct) error { return nil } +func writeCoefficients(cinfo *C.struct_jpeg_compress_struct, coefArrays *C.jvirt_barray_ptr) error { + C.jpeg_write_coefficients(cinfo, coefArrays) + return nil +} + func writeScanline(cinfo *C.struct_jpeg_compress_struct, row C.JSAMPROW, maxLines C.JDIMENSION) (line int, err error) { code := C.int(0) line = int(C.write_scanlines(cinfo, row, maxLines, &code)) diff --git a/jpeg/jpegtran.go b/jpeg/jpegtran.go index 6df4c15..bf0ee72 100644 --- a/jpeg/jpegtran.go +++ b/jpeg/jpegtran.go @@ -63,7 +63,7 @@ func JpegTran(r io.Reader, w io.Writer, options *JpegTranOptions) error { } defer destroyCompress(dstInfo) - C.jcopy_markers_setup(srcInfo, C.JCOPYOPT_ALL); + C.jcopy_markers_setup(srcInfo, C.JCOPYOPT_ALL) err = readHeader(srcInfo) if err != nil { @@ -133,13 +133,17 @@ func JpegTran(r io.Reader, w io.Writer, options *JpegTranOptions) error { dstCoefArrays := C.jtransform_adjust_parameters(srcInfo, dstInfo, srcCoefArrays, &transformOption) - C.jpeg_write_coefficients(dstInfo, dstCoefArrays) + if err := writeCoefficients(dstInfo, dstCoefArrays); err != nil { + return err + } - C.jtransform_execute_transformation(srcInfo, dstInfo, + C.jtransform_execute_transform(srcInfo, dstInfo, srcCoefArrays, &transformOption) - C.jpeg_finish_compress(dstInfo) + if err := finishCompress(dstInfo); err != nil { + return err + } return nil } diff --git a/jpeg/jpegtran_test.go b/jpeg/jpegtran_test.go index 3517858..970ed69 100644 --- a/jpeg/jpegtran_test.go +++ b/jpeg/jpegtran_test.go @@ -3,6 +3,7 @@ package jpeg_test import ( "fmt" "image" + "io/ioutil" "github.com/pixiv/go-libjpeg/jpeg" "github.com/pixiv/go-libjpeg/test/util" @@ -39,6 +40,10 @@ func testJpegTranImage(t *testing.T, source string, expected *image.RGBA, opt *j t.Fatalf("can't transform image: %v", err) } + if err := ioutil.WriteFile(util.GetOutFilePath(source), buf.Bytes(), 0644); err != nil { + t.Fatalf("can't write result image: %v", err) + } + actual, err := jpeg.DecodeIntoRGBA(&buf, &jpeg.DecoderOptions{}) if err != nil { t.Fatalf("can't decode created image: %v", err) From dffe271e850c8469b31ea71194b68ffe60441905 Mon Sep 17 00:00:00 2001 From: "Artem V. Navrotskiy" Date: Wed, 7 Oct 2020 09:28:54 +0300 Subject: [PATCH 5/6] Use transfrom constant numbers from EXIF orientation --- jpeg/jpegtran.go | 56 ++++++++++++++++++++----------------------- jpeg/jpegtran_test.go | 2 +- 2 files changed, 27 insertions(+), 31 deletions(-) diff --git a/jpeg/jpegtran.go b/jpeg/jpegtran.go index bf0ee72..c403969 100644 --- a/jpeg/jpegtran.go +++ b/jpeg/jpegtran.go @@ -16,28 +16,34 @@ import ( type Transform int +// Constants numbers get from EXIF orientation const ( - TransformAuto Transform = iota - TransformNone - TransformFlipHorizontal - TransformFlipVertical - TransformTranspose - TransformTransverse - TransformRotate90 - TransformRotate180 - TransformRotate270 + TransfromUndefined Transform = 0 + TransformNone Transform = 1 + TransformFlipHorizontal Transform = 2 + TransformRotate180 Transform = 3 + TransformFlipVertical Transform = 4 + TransformTranspose Transform = 5 + TransformRotate90 Transform = 6 + TransformTransverse Transform = 7 + TransformRotate270 Transform = 8 ) type JpegTranOptions struct { // Create progressive JPEG file Progressive bool // Fail if there is non-transformable edge blocks - Perfect bool + Perfect bool + // Autorotate flag for automagically detect transformation by EXIF orientation + AutoRotate bool + // Image transformation Transform Transform } +// Create transform options for safe image processing func NewJpegTranOptions() *JpegTranOptions { return &JpegTranOptions{ + AutoRotate: true, Progressive: true, Perfect: true, } @@ -75,27 +81,17 @@ func JpegTran(r io.Reader, w io.Writer, options *JpegTranOptions) error { transformOption.perfect = 1 } - switch options.Transform { - case TransformAuto: - switch C.jpegtran_get_orientation(srcInfo) { - case 2: - transformOption.transform = C.JXFORM_FLIP_H - case 3: - transformOption.transform = C.JXFORM_ROT_180 - case 4: - transformOption.transform = C.JXFORM_FLIP_V - case 5: - transformOption.transform = C.JXFORM_TRANSPOSE - case 6: - transformOption.transform = C.JXFORM_ROT_90 - case 7: - transformOption.transform = C.JXFORM_TRANSVERSE - case 8: - transformOption.transform = C.JXFORM_ROT_270 - default: - transformOption.transform = C.JXFORM_NONE + if options.AutoRotate { + orientation := C.jpegtran_get_orientation(srcInfo) + if orientation > 0 && orientation <= 8 { + options.Transform = Transform(orientation) + } else { + options.Transform = TransfromUndefined } - case TransformNone: + } + + switch options.Transform { + case TransformNone, TransfromUndefined: transformOption.transform = C.JXFORM_NONE case TransformFlipHorizontal: transformOption.transform = C.JXFORM_FLIP_H diff --git a/jpeg/jpegtran_test.go b/jpeg/jpegtran_test.go index 970ed69..287e84a 100644 --- a/jpeg/jpegtran_test.go +++ b/jpeg/jpegtran_test.go @@ -24,7 +24,7 @@ func TestJpegTran(t *testing.T) { testJpegTranImage(t, fmt.Sprintf("lossless_%d.jpg", i), expected, &jpeg.JpegTranOptions{ Progressive: true, Perfect: true, - Transform: jpeg.TransformAuto, + AutoRotate: true, }) } } From e12d13b0254ff758cd4f77eee1c60fbfa3d05831 Mon Sep 17 00:00:00 2001 From: "Artem V. Navrotskiy" Date: Tue, 11 Jan 2022 15:47:34 +0300 Subject: [PATCH 6/6] Fix compilation with embedded libjpeg --- jpeg/destinationManager.go | 2 +- jpeg/jpeg.go | 2 +- jpeg/sourceManager.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/jpeg/destinationManager.go b/jpeg/destinationManager.go index e09a3c7..76b7950 100644 --- a/jpeg/destinationManager.go +++ b/jpeg/destinationManager.go @@ -10,7 +10,7 @@ package jpeg /* #include #include -#include +#include "jpeglib.h" // exported from golang void destinationInit(struct jpeg_compress_struct*); diff --git a/jpeg/jpeg.go b/jpeg/jpeg.go index 892ace0..b8df018 100644 --- a/jpeg/jpeg.go +++ b/jpeg/jpeg.go @@ -12,7 +12,7 @@ package jpeg #cgo LDFLAGS: -ljpeg #include #include -#include +#include "jpeglib.h" static J_COLOR_SPACE getJCS_EXT_RGBA(void) { #ifdef JCS_ALPHA_EXTENSIONS diff --git a/jpeg/sourceManager.go b/jpeg/sourceManager.go index b1ccc47..c844751 100644 --- a/jpeg/sourceManager.go +++ b/jpeg/sourceManager.go @@ -11,7 +11,7 @@ package jpeg #include #include #include -#include +#include "jpeglib.h" // exported from golang void sourceInit(struct jpeg_decompress_struct*);