diff options
Diffstat (limited to 'src/imageutils')
-rw-r--r-- | src/imageutils/Makefile.am | 36 | ||||
-rw-r--r-- | src/imageutils/README | 2 | ||||
-rw-r--r-- | src/imageutils/asm_scale.S | 810 | ||||
-rw-r--r-- | src/imageutils/croppedqimage.cpp | 77 | ||||
-rw-r--r-- | src/imageutils/croppedqimage.h | 42 | ||||
-rw-r--r-- | src/imageutils/imageutils.cpp | 211 | ||||
-rw-r--r-- | src/imageutils/imageutils.h | 49 | ||||
-rw-r--r-- | src/imageutils/jinclude.h | 91 | ||||
-rw-r--r-- | src/imageutils/jpegcontent.cpp | 666 | ||||
-rw-r--r-- | src/imageutils/jpegcontent.h | 86 | ||||
-rw-r--r-- | src/imageutils/jpegerrormanager.h | 61 | ||||
-rw-r--r-- | src/imageutils/jpegint.h | 392 | ||||
-rw-r--r-- | src/imageutils/orient6.jpg | bin | 0 -> 8828 bytes | |||
-rw-r--r-- | src/imageutils/orientation.h | 58 | ||||
-rw-r--r-- | src/imageutils/scale.cpp | 1979 | ||||
-rw-r--r-- | src/imageutils/testjpegcontent.cpp | 256 | ||||
-rw-r--r-- | src/imageutils/transupp.c | 928 | ||||
-rw-r--r-- | src/imageutils/transupp.h | 141 |
18 files changed, 5885 insertions, 0 deletions
diff --git a/src/imageutils/Makefile.am b/src/imageutils/Makefile.am new file mode 100644 index 0000000..33c83b3 --- /dev/null +++ b/src/imageutils/Makefile.am @@ -0,0 +1,36 @@ +# QT_CLEAN_NAMESPACE is needed when building with automake, otherwise +# compilation fails in jpegcontent.cpp +AM_CPPFLAGS = -I$(srcdir) -I$(srcdir)/.. $(LIBEXIF_CFLAGS) $(all_includes) \ + -DQT_CLEAN_NAMESPACE +AM_CCASFLAGS = -I$(srcdir) $(GV_ASM_DEFS) + +KDE_CXXFLAGS = $(USE_EXCEPTIONS) + +noinst_LTLIBRARIES = libgvimageutils.la + +libgvimageutils_la_SOURCES = \ + imageutils.cpp \ + jpegcontent.cpp \ + scale.cpp \ + transupp.c \ + asm_scale.S \ + croppedqimage.cpp + +libgvimageutils_la_LIBADD = $(LIB_KDECORE) $(LIBQT) $(LIBJPEG) $(LIB_EXIV2) + +noinst_HEADERS = \ + orientation.h \ + imageutils.h \ + jpegcontent.h \ + jinclude.h \ + jpegint.h \ + transupp.h \ + jpegerrormanager.h \ + croppedqimage.h + +METASOURCES = AUTO + +check_PROGRAMS = testjpegcontent +testjpegcontent_SOURCES = testjpegcontent.cpp +testjpegcontent_LDADD = $(LIB_KFILE) libgvimageutils.la +testjpegcontent_LDFLAGS = $(all_libraries) diff --git a/src/imageutils/README b/src/imageutils/README new file mode 100644 index 0000000..335d36e --- /dev/null +++ b/src/imageutils/README @@ -0,0 +1,2 @@ +This directory contains image utilities. It contains code from the JPEGLib to +perform lossless transformations and from the ImageMagick library. diff --git a/src/imageutils/asm_scale.S b/src/imageutils/asm_scale.S new file mode 100644 index 0000000..08b43da --- /dev/null +++ b/src/imageutils/asm_scale.S @@ -0,0 +1,810 @@ +#ifdef HAVE_X86_MMX + +#ifdef __EMX__ +/* Due to strange behaviour of as.exe we use this macros */ +/* For all OS/2 coders - please use PGCC to compile this code */ +#define PR_(foo) ___##foo +#define PT_(foo,func) ___##foo,func +#define SIZE(sym) \ + .___end_##sym:; \ + .size ___##sym,.___end_##sym-___##sym; \ + .align 8; +#else +#define PR_(foo) __##foo +#define PT_(foo,func) __##foo,func +#define SIZE(sym) \ + .__end_##sym:; \ + .size __##sym,.__end_##sym-__##sym; \ + .align 8; +#endif + +/*\ +|*| MMX assembly scaling routine for Imlib2 +|*| Written by Willem Monsuwe <willem@stack.nl> +\*/ + +.text + .align 8 +.globl PR_(mimageScale_mmx_AARGBA) +/* .type PT_(mimageScale_mmx_AARGBA,@function) */ + + +/*\ Prototype: __mimageScale_mmx_AARGBA(ImlibScaleInfo *isi, DATA32 *dest, +|*| int dxx, int dyy, int dx, int dy, int dw, int dh, int dow, int sow) +\*/ + +#define isi 8(%ebp) +#define dest 12(%ebp) +#define dxx 16(%ebp) +#define dyy 20(%ebp) +#define dx 24(%ebp) +#define dy 28(%ebp) +#define dw 32(%ebp) +#define dh 36(%ebp) +#define dow 40(%ebp) +#define sow 44(%ebp) + +/*\ Local variables that didn't fit in registers \*/ +#define y -4(%ebp) +#define yp -8(%ebp) +#define yap -12(%ebp) +#define xp -16(%ebp) +#define xap -20(%ebp) +#define Cx -24(%ebp) +#define Mx -28(%ebp) +#define Cy -32(%ebp) +#define My -36(%ebp) +#define sow_4 -40(%ebp) + +/*\ When %edx points to ImlibScaleInfo, these are the members \*/ +#define xpoints (%edx) +#define ypoints 4(%edx) +#define xapoints 8(%edx) +#define yapoints 12(%edx) +#define xup_yup 16(%edx) + +PR_(mimageScale_mmx_AARGBA): + pushl %ebp + movl %esp, %ebp + subl $40, %esp + pushl %ebx + pushl %ecx + pushl %edx + pushl %edi + pushl %esi + movl isi, %edx + + /*\ Check (dw > 0) && (dh > 0) \*/ + cmpl $0, dw + jle .scale_leave + cmpl $0, dh + jle .scale_leave + + /*\ X-based array pointers point to the end; we're looping up to 0 \*/ + /*\ %edi = dest + dow * dy + dx + dw \*/ + movl dow, %eax + imull dy, %eax + addl dx, %eax + addl dw, %eax + movl dest, %edi + leal (%edi, %eax, 4), %edi + /*\ xp = xpoints + dxx + dw \*/ + movl dxx, %ebx + addl dw, %ebx + movl xpoints, %eax + leal (%eax, %ebx, 4), %eax + movl %eax, xp + /*\ xap = xapoints + dxx + dw \*/ + movl xapoints, %eax + leal (%eax, %ebx, 4), %eax + movl %eax, xap + /*\ y = dh \*/ + movl dh, %eax + movl %eax, y + /*\ yp = ypoints + dyy \*/ + movl dyy, %ebx + movl ypoints, %eax + leal (%eax, %ebx, 4), %eax + movl %eax, yp + /*\ yap = yapoints + dyy \*/ + movl yapoints, %eax + leal (%eax, %ebx, 4), %eax + movl %eax, yap + + pxor %mm7, %mm7 + + /*\ Test xup bit \*/ + movl xup_yup, %eax + sarl $1, %eax + jnc .scale_x_down + +.scale_x_up: + /*\ Test yup bit \*/ + sarl $1, %eax + jnc .scale_x_up_y_down + + +/*\ Scaling up both ways \*/ + +.scale_x_up_y_up: + movl sow, %ebx + +.up_up_loop_y: + + /*\ x = -dw \*/ + movl dw, %ecx + negl %ecx + + /*\ %eax = *yap << 4 \*/ + movl yap, %eax + movl (%eax), %eax + sall $4, %eax + jz .up_up_yap_0 + movd %eax, %mm1 + punpcklwd %mm1, %mm1 + punpckldq %mm1, %mm1 + +.up_up_loop1_x: + /*\ %esi = *yp + xp[x] \*/ + movl yp, %eax + movl (%eax), %esi + movl xp, %eax + movl (%eax, %ecx, 4), %eax + leal (%esi, %eax, 4), %esi + + /*\ %eax = xap[x] << 4 \*/ + movl xap, %eax + movl (%eax, %ecx, 4), %eax + sall $4, %eax + jz .up_up_xap_0 + + /*\ %mm0 = xap[x] << 4 \*/ + movd %eax, %mm0 + punpcklwd %mm0, %mm0 + punpckldq %mm0, %mm0 + + /*\ Load and unpack four pixels in parralel + |*| %mm2 = ptr[0], %mm3 = ptr[1] + |*| %mm4 = ptr[sow], %mm5 = ptr[sow + 1] + \*/ + movq (%esi), %mm2 + movq (%esi, %ebx, 4), %mm4 + movq %mm2, %mm3 + movq %mm4, %mm5 + punpcklbw %mm7, %mm2 + punpcklbw %mm7, %mm4 + punpckhbw %mm7, %mm3 + punpckhbw %mm7, %mm5 + + /*\ X interpolation: r = l + (r - l) * xap \*/ + psubw %mm2, %mm3 + psubw %mm4, %mm5 + psllw $4, %mm3 + psllw $4, %mm5 + pmulhw %mm0, %mm3 + pmulhw %mm0, %mm5 + paddw %mm2, %mm3 + paddw %mm4, %mm5 + /*\ Now %mm3 = I(ptr[0], ptr[1]), %mm5 = I(ptr[sow], ptr[sow + 1]) \*/ + jmp .up_up_common +.up_up_xap_0: + /*\ Load and unpack two pixels + |*| %mm3 = ptr[0], %mm5 = ptr[sow] + \*/ + movd (%esi), %mm3 + movd (%esi, %ebx, 4), %mm5 + punpcklbw %mm7, %mm3 + punpcklbw %mm7, %mm5 +.up_up_common: + /*\ Y interpolation: d = u + (d - u) * yap \*/ + psubw %mm3, %mm5 + psllw $4, %mm5 + pmulhw %mm1, %mm5 + paddw %mm3, %mm5 + packuswb %mm5, %mm5 + movd %mm5, (%edi, %ecx, 4) + + /*\ while (++x) \*/ + incl %ecx + jnz .up_up_loop1_x + jmp .up_up_yap_end +.up_up_yap_0: + +.up_up_loop2_x: + /*\ %esi = *yp + xp[x] \*/ + movl yp, %eax + movl (%eax), %esi + movl xp, %eax + movl (%eax, %ecx, 4), %eax + leal (%esi, %eax, 4), %esi + + /*\ %eax = xap[x] << 4 \*/ + movl xap, %eax + movl (%eax, %ecx, 4), %eax + sall $4, %eax + jz .up_up_0 + + /*\ %mm0 = xap[x] << 4 \*/ + movd %eax, %mm0 + punpcklwd %mm0, %mm0 + punpckldq %mm0, %mm0 + + /*\ Load and unpack two pixels in parralel + |*| %mm2 = ptr[0], %mm3 = ptr[1] + \*/ + movq (%esi), %mm2 + movq %mm2, %mm3 + punpcklbw %mm7, %mm2 + punpckhbw %mm7, %mm3 + + /*\ X interpolation: r = l + (r - l) * xap \*/ + psubw %mm2, %mm3 + psllw $4, %mm3 + pmulhw %mm0, %mm3 + paddw %mm2, %mm3 + packuswb %mm3, %mm3 + movd %mm3, (%edi, %ecx, 4) + jmp .up_up_1 +.up_up_0: + /*\ dptr[x] = *sptr \*/ + movl (%esi), %eax + movl %eax, (%edi, %ecx, 4) +.up_up_1: + incl %ecx + jnz .up_up_loop2_x + +.up_up_yap_end: + /*\ dptr += dow \*/ + movl dow, %eax + leal (%edi, %eax, 4), %edi + /*\ yap++; yp++ \*/ + addl $4, yap + addl $4, yp + /*\ while (y--) \*/ + decl y + jnz .up_up_loop_y + + jmp .scale_leave + + +/*\ Scaling down vertically \*/ + +.scale_x_up_y_down: + /*\ sow_4 = sow * 4 \*/ + movl sow, %eax + sall $2, %eax + movl %eax, sow_4 + +.up_down_loop_y: + + /*\ Setup My and Cy \*/ + movl yap, %eax + movzwl (%eax), %ebx + movl %ebx, My + movzwl 2(%eax), %eax + movl %eax, Cy + + /*\ mm4 = Cy \*/ + movd %eax, %mm4 + punpcklwd %mm4, %mm4 + punpckldq %mm4, %mm4 + /*\ mm5 = My \*/ + movd %ebx, %mm5 + punpcklwd %mm5, %mm5 + punpckldq %mm5, %mm5 + + /*\ x = -dw \*/ + movl dw, %ecx + negl %ecx +.up_down_loop_x: + /*\ %esi = *yp + xp[x] \*/ + movl yp, %eax + movl (%eax), %esi + movl xp, %eax + movl (%eax, %ecx, 4), %eax + leal (%esi, %eax, 4), %esi + + movl %esi, %eax + /*\ v = (*p * My) >> 10 \*/ + movd (%eax), %mm0 + punpcklbw %mm7, %mm0 + psllw $6, %mm0 + pmulhw %mm5, %mm0 + + /*\ i = 0x4000 - My \*/ + movl $0x4000, %ebx + subl My, %ebx + jbe 5f + jmp 2f +1: + /*\ p += sow; v += (*p * Cy) >> 10 \*/ + addl sow_4, %eax + movd (%eax), %mm1 + punpcklbw %mm7, %mm1 + psllw $6, %mm1 + pmulhw %mm4, %mm1 + paddw %mm1, %mm0 + + /*\ i -= Cy; while (i > Cy) \*/ + subl Cy, %ebx +2: + cmpl Cy, %ebx + jg 1b + + /*\ mm6 = i \*/ + movd %ebx, %mm6 + punpcklwd %mm6, %mm6 + punpckldq %mm6, %mm6 + + /*\ p += sow; v += (*p * i) >> 10 \*/ + addl sow_4, %eax + movd (%eax), %mm1 + punpcklbw %mm7, %mm1 + psllw $6, %mm1 + pmulhw %mm6, %mm1 + paddw %mm1, %mm0 +5: + /*\ %eax = xap[x] << 5 \*/ + movl xap, %eax + movl (%eax, %ecx, 4), %eax + sall $5, %eax + jz 6f + /*\ mm3 = xap[x] << 5 \*/ + movd %eax, %mm3 + punpcklwd %mm3, %mm3 + punpckldq %mm3, %mm3 + + /*\ p + 1 \*/ + movl %esi, %eax + addl $4, %eax + /*\ vv = (*p * My) >> 10 \*/ + movd (%eax), %mm2 + punpcklbw %mm7, %mm2 + psllw $6, %mm2 + pmulhw %mm5, %mm2 + + /*\ i = 0x4000 - My \*/ + movl $0x4000, %ebx + subl My, %ebx + jbe 5f + jmp 2f +1: + /*\ p += sow; vv += (*p * Cy) >> 10 \*/ + addl sow_4, %eax + movd (%eax), %mm1 + punpcklbw %mm7, %mm1 + psllw $6, %mm1 + pmulhw %mm4, %mm1 + paddw %mm1, %mm2 + + /*\ i -= Cy; while (i > Cy) \*/ + subl Cy, %ebx +2: + cmpl Cy, %ebx + jg 1b + + /*\ p += sow; v += (*p * i) >> 10 \*/ + addl sow_4, %eax + movd (%eax), %mm1 + punpcklbw %mm7, %mm1 + psllw $6, %mm1 + pmulhw %mm6, %mm1 + paddw %mm1, %mm2 +5: + /*\ v = v + (vv - v) * xap \*/ + psubw %mm0, %mm2 + psllw $3, %mm2 + pmulhw %mm3, %mm2 + paddw %mm2, %mm0 +6: + /*\ dest[x] = v >> 4 \*/ + psrlw $4, %mm0 + packuswb %mm0, %mm0 + movd %mm0, (%edi, %ecx, 4) + + /*\ while (++x) \*/ + incl %ecx + jnz .up_down_loop_x + + /*\ dptr += dow \*/ + movl dow, %eax + leal (%edi, %eax, 4), %edi + /*\ yap++; yp++ \*/ + addl $4, yap + addl $4, yp + /*\ while (y--) \*/ + decl y + jnz .up_down_loop_y + + jmp .scale_leave + +.scale_x_down: + /*\ Test yup bit \*/ + sarl $1, %eax + jnc .scale_x_down_y_down + + +/*\ Scaling down horizontally \*/ + +.scale_x_down_y_up: + /*\ sow_4 = sow * 4 \*/ + movl sow, %eax + sall $2, %eax + movl %eax, sow_4 + +.down_up_loop_y: + + /*\ %eax = *yap << 5 \*/ + movl yap, %eax + movl (%eax), %eax + sall $5, %eax + /*\ mm3 = *yap << 5 \*/ + movd %eax, %mm3 + punpcklwd %mm3, %mm3 + punpckldq %mm3, %mm3 + + /*\ x = -dw \*/ + movl dw, %ecx + negl %ecx +.down_up_loop_x: + /*\ %esi = *yp + xp[x] \*/ + movl yp, %eax + movl (%eax), %esi + movl xp, %eax + movl (%eax, %ecx, 4), %eax + leal (%esi, %eax, 4), %esi + + /*\ Setup Mx and Cx \*/ + movl xap, %eax + movzwl (%eax, %ecx, 4), %ebx + movl %ebx, Mx + movzwl 2(%eax, %ecx, 4), %eax + movl %eax, Cx + + /*\ mm4 = Cx \*/ + movd %eax, %mm4 + punpcklwd %mm4, %mm4 + punpckldq %mm4, %mm4 + /*\ mm5 = Mx \*/ + movd %ebx, %mm5 + punpcklwd %mm5, %mm5 + punpckldq %mm5, %mm5 + + movl %esi, %eax + /*\ v = (*p * Mx) >> 10 \*/ + movd (%eax), %mm0 + punpcklbw %mm7, %mm0 + psllw $6, %mm0 + pmulhw %mm5, %mm0 + + /*\ i = 0x4000 - Mx \*/ + movl $0x4000, %ebx + subl Mx, %ebx + jbe 5f + jmp 2f +1: + /*\ p += sow; v += (*p * Cx) >> 10 \*/ + addl $4, %eax + movd (%eax), %mm1 + punpcklbw %mm7, %mm1 + psllw $6, %mm1 + pmulhw %mm4, %mm1 + paddw %mm1, %mm0 + + /*\ i -= Cx; while (i > Cx) \*/ + subl Cx, %ebx +2: + cmpl Cx, %ebx + jg 1b + + /*\ mm6 = i \*/ + movd %ebx, %mm6 + punpcklwd %mm6, %mm6 + punpckldq %mm6, %mm6 + + /*\ p += sow; v += (*p * i) >> 10 \*/ + addl $4, %eax + movd (%eax), %mm1 + punpcklbw %mm7, %mm1 + psllw $6, %mm1 + pmulhw %mm6, %mm1 + paddw %mm1, %mm0 +5: + movd %mm3, %eax + testl %eax, %eax + jz 6f + /*\ p + sow \*/ + movl %esi, %eax + addl sow_4, %eax + /*\ vv = (*p * Mx) >> 10 \*/ + movd (%eax), %mm2 + punpcklbw %mm7, %mm2 + psllw $6, %mm2 + pmulhw %mm5, %mm2 + + /*\ i = 0x4000 - Mx \*/ + movl $0x4000, %ebx + subl Mx, %ebx + jbe 5f + jmp 2f +1: + /*\ p += sow; vv += (*p * Cx) >> 10 \*/ + addl $4, %eax + movd (%eax), %mm1 + punpcklbw %mm7, %mm1 + psllw $6, %mm1 + pmulhw %mm4, %mm1 + paddw %mm1, %mm2 + + /*\ i -= Cx; while (i > Cx) \*/ + subl Cx, %ebx +2: + cmpl Cx, %ebx + jg 1b + + /*\ p += sow; v += (*p * i) >> 10 \*/ + addl $4, %eax + movd (%eax), %mm1 + punpcklbw %mm7, %mm1 + psllw $6, %mm1 + pmulhw %mm6, %mm1 + paddw %mm1, %mm2 +5: + /*\ v = v + (vv - v) * yap \*/ + psubw %mm0, %mm2 + psllw $3, %mm2 + pmulhw %mm3, %mm2 + paddw %mm2, %mm0 +6: + /*\ dest[x] = v >> 4 \*/ + psrlw $4, %mm0 + packuswb %mm0, %mm0 + movd %mm0, (%edi, %ecx, 4) + + /*\ while (++x) \*/ + incl %ecx + jnz .down_up_loop_x + + /*\ dptr += dow \*/ + movl dow, %eax + leal (%edi, %eax, 4), %edi + /*\ yap++; yp++ \*/ + addl $4, yap + addl $4, yp + /*\ while (y--) \*/ + decl y + jnz .down_up_loop_y + + jmp .scale_leave + + +/*\ Scaling down both ways \*/ + +.scale_x_down_y_down: + /*\ sow_4 = sow * 4 \*/ + movl sow, %eax + sall $2, %eax + movl %eax, sow_4 + +.down_down_loop_y: + + /*\ Setup My and Cy \*/ + movl yap, %eax + movzwl (%eax), %ebx + movl %ebx, My + movzwl 2(%eax), %eax + movl %eax, Cy + + /*\ x = -dw \*/ + movl dw, %ecx + negl %ecx +.down_down_loop_x: + /*\ %esi = *yp + xp[x] \*/ + movl yp, %eax + movl (%eax), %esi + movl xp, %eax + movl (%eax, %ecx, 4), %eax + leal (%esi, %eax, 4), %esi + + /*\ Setup Mx and Cx \*/ + movl xap, %eax + movzwl (%eax, %ecx, 4), %ebx + movl %ebx, Mx + movzwl 2(%eax, %ecx, 4), %eax + movl %eax, Cx + + /*\ mm3 = Cx \*/ + movd %eax, %mm3 + punpcklwd %mm3, %mm3 + punpckldq %mm3, %mm3 + /*\ mm5 = Mx \*/ + movd %ebx, %mm5 + punpcklwd %mm5, %mm5 + punpckldq %mm5, %mm5 + + /*\ p = sptr; v = (*p * Mx) >> 9 \*/ + movl %esi, %eax + movd (%eax), %mm0 + punpcklbw %mm7, %mm0 + psllw $7, %mm0 + pmulhw %mm5, %mm0 + + /*\ i = 0x4000 - Mx \*/ + movl $0x4000, %ebx + subl Mx, %ebx + jbe 5f + jmp 2f +1: + /*\ v += (*++p * Cx) >> 9 \*/ + addl $4, %eax + movd (%eax), %mm1 + punpcklbw %mm7, %mm1 + psllw $7, %mm1 + pmulhw %mm3, %mm1 + paddw %mm1, %mm0 + + /*\ i -= Cx; while (i > Cx) \*/ + subl Cx, %ebx +2: + cmpl Cx, %ebx + jg 1b + + /*\ mm6 = i \*/ + movd %ebx, %mm6 + punpcklwd %mm6, %mm6 + punpckldq %mm6, %mm6 + + /*\ v += (*++p * i) >> 9 \*/ + addl $4, %eax + movd (%eax), %mm1 + punpcklbw %mm7, %mm1 + psllw $7, %mm1 + pmulhw %mm6, %mm1 + paddw %mm1, %mm0 +5: + /*\ v *= My \*/ + movd My, %mm4 + punpcklwd %mm4, %mm4 + punpckldq %mm4, %mm4 + psllw $2, %mm0 + pmulhw %mm4, %mm0 + + /*\ j = 0x4000 - My \*/ + movl $0x4000, %edx + subl My, %edx + jbe 6f + jmp 4f +3: + /*\ sptr += sow; p = sptr \*/ + addl sow_4, %esi + movl %esi, %eax + /*\ vx = (*p * Mx) >> 9 \*/ + movd (%eax), %mm1 + punpcklbw %mm7, %mm1 + psllw $7, %mm1 + pmulhw %mm5, %mm1 + + /*\ i = 0x4000 - Mx \*/ + movl $0x4000, %ebx + subl Mx, %ebx + jbe 5f + jmp 2f +1: + /*\ vx += (*++p * Cx) >> 9 \*/ + addl $4, %eax + movd (%eax), %mm2 + punpcklbw %mm7, %mm2 + psllw $7, %mm2 + pmulhw %mm3, %mm2 + paddw %mm2, %mm1 + + /*\ i -= Cx; while (i > Cx) \*/ + subl Cx, %ebx +2: + cmpl Cx, %ebx + jg 1b + + /*\ vx += (*++p * i) >> 9 \*/ + addl $4, %eax + movd (%eax), %mm2 + punpcklbw %mm7, %mm2 + psllw $7, %mm2 + pmulhw %mm6, %mm2 + paddw %mm2, %mm1 +5: + /*\ v += (vx * Cy) >> 14 \*/ + movd Cy, %mm4 + punpcklwd %mm4, %mm4 + punpckldq %mm4, %mm4 + psllw $2, %mm1 + pmulhw %mm4, %mm1 + paddw %mm1, %mm0 + + /*\ j -= Cy; while (j > Cy) \*/ + subl Cy, %edx +4: + cmpl Cy, %edx + jg 3b + + /*\ sptr += sow; p = sptr \*/ + addl sow_4, %esi + movl %esi, %eax + /*\ vx = (*p * Mx) >> 9 \*/ + movd (%eax), %mm1 + punpcklbw %mm7, %mm1 + psllw $7, %mm1 + pmulhw %mm5, %mm1 + + /*\ i = 0x4000 - Mx \*/ + movl $0x4000, %ebx + subl Mx, %ebx + jbe 5f + jmp 2f +1: + /*\ vx += (*++p * Cx) >> 9 \*/ + addl $4, %eax + movd (%eax), %mm2 + punpcklbw %mm7, %mm2 + psllw $7, %mm2 + pmulhw %mm3, %mm2 + paddw %mm2, %mm1 + + /*\ i -= Cx; while (i > Cx) \*/ + subl Cx, %ebx +2: + cmpl Cx, %ebx + jg 1b + + /*\ vx += (*++p * i) >> 9 \*/ + addl $4, %eax + movd (%eax), %mm2 + punpcklbw %mm7, %mm2 + psllw $7, %mm2 + pmulhw %mm6, %mm2 + paddw %mm2, %mm1 +5: + /*\ v += (vx * j) >> 14 \*/ + movd %edx, %mm4 + punpcklwd %mm4, %mm4 + punpckldq %mm4, %mm4 + psllw $2, %mm1 + pmulhw %mm4, %mm1 + paddw %mm1, %mm0 +6: + /*\ dptr[x] = mm0 >> 5 \*/ + psrlw $5, %mm0 + packuswb %mm0, %mm0 + movd %mm0, (%edi, %ecx, 4) + + /*\ while (++x) \*/ + incl %ecx + jnz .down_down_loop_x + + /*\ dptr += dow \*/ + movl dow, %eax + leal (%edi, %eax, 4), %edi + /*\ yap++; yp++ \*/ + addl $4, yap + addl $4, yp + /*\ while (y--) \*/ + decl y + jnz .down_down_loop_y + + jmp .scale_leave + +.scale_leave: + emms + popl %esi + popl %edi + popl %edx + popl %ecx + popl %ebx + movl %ebp, %esp + popl %ebp + ret + +SIZE(mimageScale_mmx_AARGBA) + +#endif + +.section .note.GNU-stack,"",%progbits diff --git a/src/imageutils/croppedqimage.cpp b/src/imageutils/croppedqimage.cpp new file mode 100644 index 0000000..5ed2aab --- /dev/null +++ b/src/imageutils/croppedqimage.cpp @@ -0,0 +1,77 @@ +/* + + Copyright (C) 2005 Lubos Lunak <l.lunak@kde.org> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "croppedqimage.h" + +namespace ImageUtils +{ + +// This class is used in ImageView::performPaint(). Just using QImage::copy( QRect ) +// takes a significant time with very large images. So instead of copying the image data +// just create CroppedQImage which fakes a subimage by manipulating its scanline pointers. +// That will of course break if something doesn't use scanlines but accesses the image +// data directly, QImage::copy() being the most notable case. There are two ways +// to handle that: 1) It is possible to manually call normalize() which will make +// CroppedQImage copy the image data and own it, just like proper QImage. 2) CroppedQImage +// has as a data member also QImage holding the original image. This ensures that all +// the original image data are still available for the whole lifetime of CroppedQImage. + +CroppedQImage::CroppedQImage( const QImage& im, const QRect& rect ) + : QImage( rect.size(), im.depth(), im.numColors(), im.bitOrder()) + , orig( im ) + { + if( im.isNull()) + return; + memcpy( colorTable(), im.colorTable(), im.numColors() * sizeof( QRgb )); + setAlphaBuffer( im.hasAlphaBuffer()); + setDotsPerMeterX( im.dotsPerMeterX()); + setDotsPerMeterY( im.dotsPerMeterY()); + //data->offset = im.offset(); + // make scanlines point to right places in the original QImage + for( int i = 0; + i < height(); + ++i ) + jumpTable()[ i ] = im.scanLine( rect.y() + i ) + rect.x() * ( depth() / 8 ); + } + +CroppedQImage& CroppedQImage::operator= ( const QImage& im ) + { + QImage::operator=( im ); + return *this; + } + +void CroppedQImage::normalize() + { + // is it a normal QImage with its own data? + uchar* firstdata = ( uchar* )( jumpTable() + height()); + if( scanLine( 0 ) == firstdata ) + return; + // copy the image data to our own data and make scanlines point properly there + for( int i = 0; + i < height(); + ++i ) + { + uchar* oldline = scanLine( i ); + jumpTable()[ i ] = firstdata + i * bytesPerLine(); + memcpy( scanLine( i ), oldline, bytesPerLine()); + } + } + +} // namespace diff --git a/src/imageutils/croppedqimage.h b/src/imageutils/croppedqimage.h new file mode 100644 index 0000000..4d93840 --- /dev/null +++ b/src/imageutils/croppedqimage.h @@ -0,0 +1,42 @@ +/* + + Copyright (C) 2005 Lubos Lunak <l.lunak@kde.org> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef CROPPED_QIMAGE_H +#define CROPPED_QIMAGE_H + +#include <qimage.h> + +namespace ImageUtils +{ + +class CroppedQImage + : public QImage + { + public: + CroppedQImage( const QImage& im, const QRect& rect ); + CroppedQImage& operator= ( const QImage& im ); + void normalize(); + private: + QImage orig; + }; + +} // namespace + +#endif diff --git a/src/imageutils/imageutils.cpp b/src/imageutils/imageutils.cpp new file mode 100644 index 0000000..c9d04ca --- /dev/null +++ b/src/imageutils/imageutils.cpp @@ -0,0 +1,211 @@ +// vim: set tabstop=4 shiftwidth=4 noexpandtab +/* +Gwenview - A simple image viewer for KDE +Copyright 2000-2004 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +#include <math.h> + +// Qt +#include <qimage.h> +#include <qwmatrix.h> + +// KDE +#include <kdebug.h> +#include <kdeversion.h> +#include <kglobal.h> + +// Local +#include "imageutils/orientation.h" + +namespace ImageUtils { + + +QWMatrix transformMatrix(Orientation orientation) { + QWMatrix matrix; + switch (orientation) { + case NOT_AVAILABLE: + case NORMAL: + break; + + case HFLIP: + matrix.scale(-1,1); + break; + + case ROT_180: + matrix.rotate(180); + break; + + case VFLIP: + matrix.scale(1,-1); + break; + + case TRANSPOSE: + matrix.scale(-1,1); + matrix.rotate(90); + break; + + case ROT_90: + matrix.rotate(90); + break; + + case TRANSVERSE: + matrix.scale(1,-1); + matrix.rotate(90); + break; + + case ROT_270: + matrix.rotate(270); + break; + } + + return matrix; +} + + +QImage transform(const QImage& img, Orientation orientation) { + if (orientation != NOT_AVAILABLE && orientation != NORMAL) { + return img.xForm(transformMatrix(orientation)); + } else { + return img; + } +} + + +inline +int changeBrightness( int value, int brightness ) + { + return KCLAMP( value + brightness * 255 / 100, 0, 255 ); + } + +inline +int changeContrast( int value, int contrast ) + { + return KCLAMP((( value - 127 ) * contrast / 100 ) + 127, 0, 255 ); + } + +inline +int changeGamma( int value, int gamma ) + { + return KCLAMP( int( pow( value / 255.0, 100.0 / gamma ) * 255 ), 0, 255 ); + } + +inline +int changeUsingTable( int value, const int table[] ) + { + return table[ value ]; + } + +/* + Applies either brightness, contrast or gamma conversion on the image. + If the image is not truecolor, the color table is changed. If it is + truecolor, every pixel has to be changed. In order to make it as fast + as possible, alpha value is converted only if necessary. Additionally, + since color components (red/green/blue/alpha) can have only 256 values + but images usually have many pixels, a conversion table is first + created for every color component value, and pixels are converted + using this table. +*/ + +template< int operation( int, int ) > +static +QImage changeImage( const QImage& image, int value ) + { + QImage im = image; + im.detach(); + if( im.numColors() == 0 ) /* truecolor */ + { + if( im.depth() != 32 ) /* just in case */ + im = im.convertDepth( 32 ); + int table[ 256 ]; + for( int i = 0; + i < 256; + ++i ) + table[ i ] = operation( i, value ); + if( im.hasAlphaBuffer()) + { + for( int y = 0; + y < im.height(); + ++y ) + { + QRgb* line = reinterpret_cast< QRgb* >( im.scanLine( y )); + for( int x = 0; + x < im.width(); + ++x ) + line[ x ] = qRgba( changeUsingTable( qRed( line[ x ] ), table ), + changeUsingTable( qGreen( line[ x ] ), table ), + changeUsingTable( qBlue( line[ x ] ), table ), + changeUsingTable( qAlpha( line[ x ] ), table )); + } + } + else + { + for( int y = 0; + y < im.height(); + ++y ) + { + QRgb* line = reinterpret_cast< QRgb* >( im.scanLine( y )); + for( int x = 0; + x < im.width(); + ++x ) + line[ x ] = qRgb( changeUsingTable( qRed( line[ x ] ), table ), + changeUsingTable( qGreen( line[ x ] ), table ), + changeUsingTable( qBlue( line[ x ] ), table )); + } + } + } + else + { + QRgb* colors = im.colorTable(); + for( int i = 0; + i < im.numColors(); + ++i ) + colors[ i ] = qRgb( operation( qRed( colors[ i ] ), value ), + operation( qGreen( colors[ i ] ), value ), + operation( qBlue( colors[ i ] ), value )); + } + return im; + } + + +// brightness is multiplied by 100 in order to avoid floating point numbers +QImage changeBrightness( const QImage& image, int brightness ) + { + if( brightness == 0 ) // no change + return image; + return changeImage< changeBrightness >( image, brightness ); + } + + +// contrast is multiplied by 100 in order to avoid floating point numbers +QImage changeContrast( const QImage& image, int contrast ) + { + if( contrast == 100 ) // no change + return image; + return changeImage< changeContrast >( image, contrast ); + } + +// gamma is multiplied by 100 in order to avoid floating point numbers +QImage changeGamma( const QImage& image, int gamma ) + { + if( gamma == 100 ) // no change + return image; + return changeImage< changeGamma >( image, gamma ); + } + +} // Namespace + diff --git a/src/imageutils/imageutils.h b/src/imageutils/imageutils.h new file mode 100644 index 0000000..62b757c --- /dev/null +++ b/src/imageutils/imageutils.h @@ -0,0 +1,49 @@ +// vim: set tabstop=4 shiftwidth=4 noexpandtab +/* +Gwenview - A simple image viewer for KDE +Copyright 2000-2004 Aurélien Gâteau + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +#ifndef IMAGEUTILS_H +#define IMAGEUTILS_H + +// Qt +#include <qimage.h> + +// Local +#include "imageutils/orientation.h" + +namespace ImageUtils { + enum SmoothAlgorithm { SMOOTH_NONE, SMOOTH_FAST, SMOOTH_NORMAL, SMOOTH_BEST }; + + QImage scale(const QImage& image, int width, int height, + SmoothAlgorithm alg, QImage::ScaleMode mode = QImage::ScaleFree, double blur = 1.0); + + int extraScalePixels( SmoothAlgorithm alg, double zoom, double blur = 1.0 ); + + QImage transform(const QImage& img, Orientation orientation); + + QImage changeBrightness( const QImage& image, int brightness ); + + QImage changeContrast( const QImage& image, int contrast ); + + QImage changeGamma( const QImage& image, int gamma ); + + QWMatrix transformMatrix(Orientation orientation); +} + +#endif diff --git a/src/imageutils/jinclude.h b/src/imageutils/jinclude.h new file mode 100644 index 0000000..0a4f151 --- /dev/null +++ b/src/imageutils/jinclude.h @@ -0,0 +1,91 @@ +/* + * jinclude.h + * + * Copyright (C) 1991-1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file exists to provide a single place to fix any problems with + * including the wrong system include files. (Common problems are taken + * care of by the standard jconfig symbols, but on really weird systems + * you may have to edit this file.) + * + * NOTE: this file is NOT intended to be included by applications using the + * JPEG library. Most applications need only include jpeglib.h. + */ + + +/* Include auto-config file to find out which system include files we need. */ + +#include "jconfig.h" /* auto configuration options */ +#define JCONFIG_INCLUDED /* so that jpeglib.h doesn't do it again */ + +/* + * We need the NULL macro and size_t typedef. + * On an ANSI-conforming system it is sufficient to include <stddef.h>. + * Otherwise, we get them from <stdlib.h> or <stdio.h>; we may have to + * pull in <sys/types.h> as well. + * Note that the core JPEG library does not require <stdio.h>; + * only the default error handler and data source/destination modules do. + * But we must pull it in because of the references to FILE in jpeglib.h. + * You can remove those references if you want to compile without <stdio.h>. + */ + +#ifdef HAVE_STDDEF_H +#include <stddef.h> +#endif + +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif + +#ifdef NEED_SYS_TYPES_H +#include <sys/types.h> +#endif + +#include <stdio.h> + +/* + * We need memory copying and zeroing functions, plus strncpy(). + * ANSI and System V implementations declare these in <string.h>. + * BSD doesn't have the mem() functions, but it does have bcopy()/bzero(). + * Some systems may declare memset and memcpy in <memory.h>. + * + * NOTE: we assume the size parameters to these functions are of type size_t. + * Change the casts in these macros if not! + */ + +#ifdef NEED_BSD_STRINGS + +#include <strings.h> +#define MEMZERO(target,size) bzero((void *)(target), (size_t)(size)) +#define MEMCOPY(dest,src,size) bcopy((const void *)(src), (void *)(dest), (size_t)(size)) + +#else /* not BSD, assume ANSI/SysV string lib */ + +#include <string.h> +#define MEMZERO(target,size) memset((void *)(target), 0, (size_t)(size)) +#define MEMCOPY(dest,src,size) memcpy((void *)(dest), (const void *)(src), (size_t)(size)) + +#endif + +/* + * In ANSI C, and indeed any rational implementation, size_t is also the + * type returned by sizeof(). However, it seems there are some irrational + * implementations out there, in which sizeof() returns an int even though + * size_t is defined as long or unsigned long. To ensure consistent results + * we always use this SIZEOF() macro in place of using sizeof() directly. + */ + +#define SIZEOF(object) ((size_t) sizeof(object)) + +/* + * The modules that use fread() and fwrite() always invoke them through + * these macros. On some systems you may need to twiddle the argument casts. + * CAUTION: argument order is different from underlying functions! + */ + +#define JFREAD(file,buf,sizeofbuf) \ + ((size_t) fread((void *) (buf), (size_t) 1, (size_t) (sizeofbuf), (file))) +#define JFWRITE(file,buf,sizeofbuf) \ + ((size_t) fwrite((const void *) (buf), (size_t) 1, (size_t) (sizeofbuf), (file))) diff --git a/src/imageutils/jpegcontent.cpp b/src/imageutils/jpegcontent.cpp new file mode 100644 index 0000000..b44b14d --- /dev/null +++ b/src/imageutils/jpegcontent.cpp @@ -0,0 +1,666 @@ +// vim: set tabstop=4 shiftwidth=4 noexpandtab: +/* +Gwenview - A simple image viewer for KDE +Copyright 2000-2004 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// System +#include <math.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +extern "C" { +#include <jpeglib.h> +#include "transupp.h" +} + +// Qt +#include <qbuffer.h> +#include <qfile.h> +#include <qimage.h> +#include <qmap.h> +#include <qwmatrix.h> + +// KDE +#include <kdebug.h> + +// Exiv2 +#include <exiv2/exif.hpp> +#include <exiv2/image.hpp> + +// Local +#include "imageutils/imageutils.h" +#include "imageutils/jpegcontent.h" +#include "imageutils/jpegerrormanager.h" + +namespace ImageUtils { + +const int INMEM_DST_DELTA=4096; + + +//------------------------------------------ +// +// In-memory data source manager for libjpeg +// +//------------------------------------------ +struct inmem_src_mgr : public jpeg_source_mgr { + QByteArray* mInput; +}; + +void inmem_init_source(j_decompress_ptr cinfo) { + inmem_src_mgr* src=(inmem_src_mgr*)(cinfo->src); + src->next_input_byte=(const JOCTET*)( src->mInput->data() ); + src->bytes_in_buffer=src->mInput->size(); +} + +/** + * If this function is called, it means the JPEG file is broken. We feed the + * decoder with fake EOI has specified in the libjpeg documentation. + */ +int inmem_fill_input_buffer(j_decompress_ptr cinfo) { + static JOCTET fakeEOI[2]={ JOCTET(0xFF), JOCTET(JPEG_EOI)}; + kdWarning() << k_funcinfo << " Image is incomplete" << endl; + cinfo->src->next_input_byte=fakeEOI; + cinfo->src->bytes_in_buffer=2; + return true; +} + +void inmem_skip_input_data(j_decompress_ptr cinfo, long num_bytes) { + if (num_bytes<=0) return; + Q_ASSERT(num_bytes>=long(cinfo->src->bytes_in_buffer)); + cinfo->src->next_input_byte+=num_bytes; + cinfo->src->bytes_in_buffer-=num_bytes; +} + +void inmem_term_source(j_decompress_ptr /*cinfo*/) { +} + + +//----------------------------------------------- +// +// In-memory data destination manager for libjpeg +// +//----------------------------------------------- +struct inmem_dest_mgr : public jpeg_destination_mgr { + QByteArray* mOutput; + + void dump() { + kdDebug() << "dest_mgr:\n"; + kdDebug() << "- next_output_byte: " << next_output_byte << endl; + kdDebug() << "- free_in_buffer: " << free_in_buffer << endl; + kdDebug() << "- output size: " << mOutput->size() << endl; + } +}; + +void inmem_init_destination(j_compress_ptr cinfo) { + inmem_dest_mgr* dest=(inmem_dest_mgr*)(cinfo->dest); + if (dest->mOutput->size()==0) { + bool result=dest->mOutput->resize(INMEM_DST_DELTA); + Q_ASSERT(result); + } + dest->free_in_buffer=dest->mOutput->size(); + dest->next_output_byte=(JOCTET*)(dest->mOutput->data() ); +} + +int inmem_empty_output_buffer(j_compress_ptr cinfo) { + inmem_dest_mgr* dest=(inmem_dest_mgr*)(cinfo->dest); + bool result=dest->mOutput->resize(dest->mOutput->size() + INMEM_DST_DELTA); + Q_ASSERT(result); + dest->next_output_byte=(JOCTET*)( dest->mOutput->data() + dest->mOutput->size() - INMEM_DST_DELTA ); + dest->free_in_buffer=INMEM_DST_DELTA; + + return true; +} + +void inmem_term_destination(j_compress_ptr cinfo) { + inmem_dest_mgr* dest=(inmem_dest_mgr*)(cinfo->dest); + int finalSize=dest->next_output_byte - (JOCTET*)(dest->mOutput->data()); + Q_ASSERT(finalSize>=0); + dest->mOutput->resize(finalSize); +} + + +//--------------------- +// +// JPEGContent::Private +// +//--------------------- +struct JPEGContent::Private { + QByteArray mRawData; + QSize mSize; + QString mComment; + QString mAperture; + QString mExposureTime; + QString mFocalLength; + QString mIso; + + bool mPendingTransformation; + QWMatrix mTransformMatrix; + Exiv2::ExifData mExifData; + + Private() { + mPendingTransformation = false; + } + + void setupInmemSource(j_decompress_ptr cinfo) { + Q_ASSERT(!cinfo->src); + inmem_src_mgr* src = (inmem_src_mgr*) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, + sizeof(inmem_src_mgr)); + cinfo->src=(struct jpeg_source_mgr*)(src); + + src->init_source=inmem_init_source; + src->fill_input_buffer=inmem_fill_input_buffer; + src->skip_input_data=inmem_skip_input_data; + src->resync_to_restart=jpeg_resync_to_restart; + src->term_source=inmem_term_source; + + src->mInput=&mRawData; + } + + + void setupInmemDestination(j_compress_ptr cinfo, QByteArray* outputData) { + Q_ASSERT(!cinfo->dest); + inmem_dest_mgr* dest = (inmem_dest_mgr*) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, + sizeof(inmem_dest_mgr)); + cinfo->dest=(struct jpeg_destination_mgr*)(dest); + + dest->init_destination=inmem_init_destination; + dest->empty_output_buffer=inmem_empty_output_buffer; + dest->term_destination=inmem_term_destination; + + dest->mOutput=outputData; + } + bool readSize() { + struct jpeg_decompress_struct srcinfo; + jpeg_saved_marker_ptr mark; + + // Init JPEG structs + JPEGErrorManager errorManager; + + // Initialize the JPEG decompression object + srcinfo.err = &errorManager; + jpeg_create_decompress(&srcinfo); + if (setjmp(errorManager.jmp_buffer)) { + kdError() << k_funcinfo << "libjpeg fatal error\n"; + return false; + } + + // Specify data source for decompression + setupInmemSource(&srcinfo); + + // Read the header + jcopy_markers_setup(&srcinfo, JCOPYOPT_ALL); + int result=jpeg_read_header(&srcinfo, true); + if (result!=JPEG_HEADER_OK) { + kdError() << "Could not read jpeg header\n"; + jpeg_destroy_decompress(&srcinfo); + return false; + } + mSize=QSize(srcinfo.image_width, srcinfo.image_height); + + jpeg_destroy_decompress(&srcinfo); + return true; + } +}; + + +//------------ +// +// JPEGContent +// +//------------ +JPEGContent::JPEGContent() { + d=new JPEGContent::Private(); +} + + +JPEGContent::~JPEGContent() { + delete d; +} + + +bool JPEGContent::load(const QString& path) { + QFile file(path); + if (!file.open(IO_ReadOnly)) { + kdError() << "Could not open '" << path << "' for reading\n"; + return false; + } + return loadFromData(file.readAll()); +} + + +bool JPEGContent::loadFromData(const QByteArray& data) { + d->mPendingTransformation = false; + d->mTransformMatrix.reset(); + + d->mRawData = data; + if (d->mRawData.size()==0) { + kdError() << "No data\n"; + return false; + } + + if (!d->readSize()) return false; + + Exiv2::Image::AutoPtr image; + try { + image = Exiv2::ImageFactory::open((unsigned char*)data.data(), data.size()); + image->readMetadata(); + } catch (Exiv2::Error&) { + kdError() << "Could not load image with Exiv2\n"; + return false; + } + + d->mExifData = image->exifData(); + d->mComment = QString::fromUtf8( image->comment().c_str() ); + + d->mAperture=aperture(); + d->mExposureTime=exposureTime(); + d->mIso=iso(); + d->mFocalLength=iso(); + + // Adjust the size according to the orientation + switch (orientation()) { + case TRANSPOSE: + case ROT_90: + case TRANSVERSE: + case ROT_270: + d->mSize.transpose(); + break; + default: + break; + } + + return true; +} + + +Orientation JPEGContent::orientation() const { + Exiv2::ExifKey key("Exif.Image.Orientation"); + Exiv2::ExifData::iterator it = d->mExifData.findKey(key); + if (it == d->mExifData.end()) { + return NOT_AVAILABLE; + } + return Orientation( it->toLong() ); +} + + +int JPEGContent::dotsPerMeterX() const { + return dotsPerMeter("XResolution"); +} + + +int JPEGContent::dotsPerMeterY() const { + return dotsPerMeter("YResolution"); +} + + +int JPEGContent::dotsPerMeter(const QString& keyName) const { + Exiv2::ExifKey keyResUnit("Exif.Image.ResolutionUnit"); + Exiv2::ExifData::iterator it = d->mExifData.findKey(keyResUnit); + if (it == d->mExifData.end()) { + return 0; + } + int res = it->toLong(); + QString keyVal = "Exif.Image." + keyName; + Exiv2::ExifKey keyResolution(keyVal.ascii()); + it = d->mExifData.findKey(keyResolution); + if (it == d->mExifData.end()) { + return 0; + } + // The unit for measuring XResolution and YResolution. The same unit is used for both XResolution and YResolution. + // If the image resolution in unknown, 2 (inches) is designated. + // Default = 2 + // 2 = inches + // 3 = centimeters + // Other = reserved + const float INCHESPERMETER = (100. / 2.54); + Exiv2::Rational r = it->toRational(); + if (r.second == 0) { + // a rational with 0 as second will make hang toLong() conversion + r.second = 1; + } + switch (res) { + case 3: // dots per cm + return int(float(r.first) * 100 / float(r.second)); + default: // dots per inch + return int(float(r.first) * INCHESPERMETER / float(r.second)); + } + + return 0; +} + + +void JPEGContent::resetOrientation() { + Exiv2::ExifKey key("Exif.Image.Orientation"); + Exiv2::ExifData::iterator it = d->mExifData.findKey(key); + if (it == d->mExifData.end()) { + return; + } + + *it = uint16_t(ImageUtils::NORMAL); +} + + +QSize JPEGContent::size() const { + return d->mSize; +} + + +QString JPEGContent::comment() const { + return d->mComment; +} + +QString JPEGContent::getExifInformation(const QString exifkey) const { + QString ret; + + Exiv2::ExifKey key(exifkey.latin1()); + Exiv2::ExifData::iterator it = d->mExifData.findKey(key); + + if (it != d->mExifData.end()) { + std::ostringstream outputString; + outputString << *it; + ret=QString(outputString.str().c_str()); + } + else { + ret="n/a"; + } + return ret; +} + +QString JPEGContent::aperture() const { + d->mAperture=getExifInformation("Exif.Photo.FNumber"); + return d->mAperture; +} + +QString JPEGContent::exposureTime() const { + d->mExposureTime=getExifInformation("Exif.Photo.ExposureTime"); + return d->mExposureTime; +} + +QString JPEGContent::iso() const { + d->mIso=getExifInformation("Exif.Photo.ISOSpeedRatings"); + return d->mIso; +} + +QString JPEGContent::focalLength() const { + d->mFocalLength=getExifInformation("Exif.Photo.FocalLength"); + return d->mFocalLength; +} + +void JPEGContent::setComment(const QString& comment) { + d->mComment = comment; +} + +static QWMatrix createRotMatrix(int angle) { + QWMatrix matrix; + matrix.rotate(angle); + return matrix; +} + + +static QWMatrix createScaleMatrix(int dx, int dy) { + QWMatrix matrix; + matrix.scale(dx, dy); + return matrix; +} + + + +struct OrientationInfo { + OrientationInfo() {} + OrientationInfo(Orientation o, QWMatrix m, JXFORM_CODE j) + : orientation(o), matrix(m), jxform(j) {} + + Orientation orientation; + QWMatrix matrix; + JXFORM_CODE jxform; +}; +typedef QValueList<OrientationInfo> OrientationInfoList; + +static const OrientationInfoList& orientationInfoList() { + static OrientationInfoList list; + if (list.size() == 0) { + QWMatrix rot90 = createRotMatrix(90); + QWMatrix hflip = createScaleMatrix(-1, 1); + QWMatrix vflip = createScaleMatrix(1, -1); + + list + << OrientationInfo(NOT_AVAILABLE, QWMatrix(), JXFORM_NONE) + << OrientationInfo(NORMAL, QWMatrix(), JXFORM_NONE) + << OrientationInfo(HFLIP, hflip, JXFORM_FLIP_H) + << OrientationInfo(ROT_180, createRotMatrix(180), JXFORM_ROT_180) + << OrientationInfo(VFLIP, vflip, JXFORM_FLIP_V) + << OrientationInfo(TRANSPOSE, hflip * rot90, JXFORM_TRANSPOSE) + << OrientationInfo(ROT_90, rot90, JXFORM_ROT_90) + << OrientationInfo(TRANSVERSE, vflip * rot90, JXFORM_TRANSVERSE) + << OrientationInfo(ROT_270, createRotMatrix(270), JXFORM_ROT_270) + ; + } + return list; +} + + +void JPEGContent::transform(Orientation orientation) { + if (orientation != NOT_AVAILABLE && orientation != NORMAL) { + d->mPendingTransformation = true; + OrientationInfoList::ConstIterator it(orientationInfoList().begin()), end(orientationInfoList().end()); + for (; it!=end; ++it) { + if ( (*it).orientation == orientation ) { + d->mTransformMatrix = (*it).matrix * d->mTransformMatrix; + break; + } + } + if (it == end) { + kdWarning() << k_funcinfo << "Could not find matrix for orientation\n"; + } + } +} + + +#if 0 +static void dumpMatrix(const QWMatrix& matrix) { + kdDebug() << "matrix | " << matrix.m11() << ", " << matrix.m12() << " |\n"; + kdDebug() << " | " << matrix.m21() << ", " << matrix.m22() << " |\n"; + kdDebug() << " ( " << matrix.dx() << ", " << matrix.dy() << " )\n"; +} +#endif + + +static bool matricesAreSame(const QWMatrix& m1, const QWMatrix& m2, double tolerance) { + return fabs( m1.m11() - m2.m11() ) < tolerance + && fabs( m1.m12() - m2.m12() ) < tolerance + && fabs( m1.m21() - m2.m21() ) < tolerance + && fabs( m1.m22() - m2.m22() ) < tolerance + && fabs( m1.dx() - m2.dx() ) < tolerance + && fabs( m1.dy() - m2.dy() ) < tolerance; +} + + +static JXFORM_CODE findJxform(const QWMatrix& matrix) { + OrientationInfoList::ConstIterator it(orientationInfoList().begin()), end(orientationInfoList().end()); + for (; it!=end; ++it) { + if ( matricesAreSame( (*it).matrix, matrix, 0.001) ) { + return (*it).jxform; + } + } + kdWarning() << "findJxform: failed\n"; + return JXFORM_NONE; +} + + +void JPEGContent::applyPendingTransformation() { + if (d->mRawData.size()==0) { + kdError() << "No data loaded\n"; + return; + } + + // The following code is inspired by jpegtran.c from the libjpeg + + // Init JPEG structs + struct jpeg_decompress_struct srcinfo; + struct jpeg_compress_struct dstinfo; + jvirt_barray_ptr * src_coef_arrays; + jvirt_barray_ptr * dst_coef_arrays; + + // Initialize the JPEG decompression object + JPEGErrorManager srcErrorManager; + srcinfo.err = &srcErrorManager; + jpeg_create_decompress(&srcinfo); + if (setjmp(srcErrorManager.jmp_buffer)) { + kdError() << k_funcinfo << "libjpeg error in src\n"; + return; + } + + // Initialize the JPEG compression object + JPEGErrorManager dstErrorManager; + dstinfo.err = &dstErrorManager; + jpeg_create_compress(&dstinfo); + if (setjmp(dstErrorManager.jmp_buffer)) { + kdError() << k_funcinfo << "libjpeg error in dst\n"; + return; + } + + // Specify data source for decompression + d->setupInmemSource(&srcinfo); + + // Enable saving of extra markers that we want to copy + jcopy_markers_setup(&srcinfo, JCOPYOPT_ALL); + + (void) jpeg_read_header(&srcinfo, TRUE); + + // Init transformation + jpeg_transform_info transformoption; + transformoption.transform = findJxform(d->mTransformMatrix); + transformoption.force_grayscale = false; + transformoption.trim = false; + jtransform_request_workspace(&srcinfo, &transformoption); + + /* Read source file as DCT coefficients */ + src_coef_arrays = jpeg_read_coefficients(&srcinfo); + + /* Initialize destination compression parameters from source values */ + jpeg_copy_critical_parameters(&srcinfo, &dstinfo); + + /* Adjust destination parameters if required by transform options; + * also find out which set of coefficient arrays will hold the output. + */ + dst_coef_arrays = jtransform_adjust_parameters(&srcinfo, &dstinfo, + src_coef_arrays, + &transformoption); + + /* Specify data destination for compression */ + QByteArray output; + output.resize(d->mRawData.size()); + d->setupInmemDestination(&dstinfo, &output); + + /* Start compressor (note no image data is actually written here) */ + jpeg_write_coefficients(&dstinfo, dst_coef_arrays); + + /* Copy to the output file any extra markers that we want to preserve */ + jcopy_markers_execute(&srcinfo, &dstinfo, JCOPYOPT_ALL); + + /* Execute image transformation, if any */ + jtransform_execute_transformation(&srcinfo, &dstinfo, + src_coef_arrays, + &transformoption); + + /* Finish compression and release memory */ + jpeg_finish_compress(&dstinfo); + jpeg_destroy_compress(&dstinfo); + (void) jpeg_finish_decompress(&srcinfo); + jpeg_destroy_decompress(&srcinfo); + + // Set rawData to our new JPEG + d->mRawData = output; +} + + +QImage JPEGContent::thumbnail() const { + QImage image; + if (!d->mExifData.empty()) { + Exiv2::DataBuf thumbnail = d->mExifData.copyThumbnail(); + image.loadFromData(thumbnail.pData_, thumbnail.size_); + } + return image; +} + + +void JPEGContent::setThumbnail(const QImage& thumbnail) { + if (d->mExifData.empty()) { + return; + } + + QByteArray array; + QBuffer buffer(array); + buffer.open(IO_WriteOnly); + QImageIO iio(&buffer, "JPEG"); + iio.setImage(thumbnail); + if (!iio.write()) { + kdError() << "Could not write thumbnail\n"; + return; + } + + d->mExifData.setJpegThumbnail((unsigned char*)array.data(), array.size()); +} + + +bool JPEGContent::save(const QString& path) { + QFile file(path); + if (!file.open(IO_WriteOnly)) { + kdError() << "Could not open '" << path << "' for writing\n"; + return false; + } + + return save(&file); +} + + +bool JPEGContent::save(QFile* file) { + if (d->mRawData.size()==0) { + kdError() << "No data to store in '" << file->name() << "'\n"; + return false; + } + + if (d->mPendingTransformation) { + applyPendingTransformation(); + d->mPendingTransformation = false; + } + + Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open((unsigned char*)d->mRawData.data(), d->mRawData.size()); + + // Store Exif info + image->setExifData(d->mExifData); + image->setComment(d->mComment.utf8().data()); + image->writeMetadata(); + + // Update mRawData + Exiv2::BasicIo& io = image->io(); + d->mRawData.resize(io.size()); + io.read((unsigned char*)d->mRawData.data(), io.size()); + + QDataStream stream(file); + stream.writeRawBytes(d->mRawData.data(), d->mRawData.size()); + + // Make sure we are up to date + loadFromData(d->mRawData); + return true; +} + + +} // namespace diff --git a/src/imageutils/jpegcontent.h b/src/imageutils/jpegcontent.h new file mode 100644 index 0000000..40af44d --- /dev/null +++ b/src/imageutils/jpegcontent.h @@ -0,0 +1,86 @@ +// vim: set tabstop=4 shiftwidth=4 noexpandtab +/* +Gwenview - A simple image viewer for KDE +Copyright 2000-2004 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +#ifndef JPEGCONTENT_H +#define JPEGCONTENT_H + +// Qt +#include <qcstring.h> + +// Local +#include <imageutils/orientation.h> + +#include "../gvcore/libgwenview_export.h" + +class QImage; +class QString; +class QFile; + +namespace ImageUtils { + + +class LIBGWENVIEW_EXPORT JPEGContent { +public: + JPEGContent(); + ~JPEGContent(); + + Orientation orientation() const; + void resetOrientation(); + + int dotsPerMeterX() const; + int dotsPerMeterY() const; + + QSize size() const; + + QString comment() const; + void setComment(const QString&); + + QString aperture() const; + QString exposureTime() const; + QString iso() const; + QString focalLength() const; + + QString getExifInformation(const QString exifkey) const; + + void transform(Orientation); + + QImage thumbnail() const; + void setThumbnail(const QImage&); + + bool load(const QString& file); + bool loadFromData(const QByteArray& rawData); + bool save(const QString& file); + bool save(QFile*); + +private: + struct Private; + Private *d; + + JPEGContent(const JPEGContent&); + void operator=(const JPEGContent&); + void applyPendingTransformation(); + int dotsPerMeter(const QString& keyName) const; +}; + + +} // namespace + + +#endif /* JPEGCONTENT_H */ diff --git a/src/imageutils/jpegerrormanager.h b/src/imageutils/jpegerrormanager.h new file mode 100644 index 0000000..e4f82bb --- /dev/null +++ b/src/imageutils/jpegerrormanager.h @@ -0,0 +1,61 @@ +// vim: set tabstop=4 shiftwidth=4 noexpandtab +/* +Gwenview - A simple image viewer for KDE +Copyright 2000-2004 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +#ifndef JPEGERRORMANAGER_H +#define JPEGERRORMANAGER_H + +#include <setjmp.h> + +extern "C" { +#define XMD_H +#include <jpeglib.h> +#undef const +} + +namespace ImageUtils { + +/** + * A simple error manager which overrides jpeg_error_mgr.error_exit to avoid + * calls to exit(). It uses setjmp, which I don't like, but I don't fill like + * introducing exceptions to the code base for now. + * + * In order to use it, give an instance of it to jpeg_decompress_struct.err, + * then call setjmp(errorManager.jmp_buffer) + */ +struct JPEGErrorManager : public jpeg_error_mgr { + JPEGErrorManager() : jpeg_error_mgr() { + jpeg_std_error(this); + error_exit=errorExitCallBack; + } + + jmp_buf jmp_buffer; + + static void errorExitCallBack (j_common_ptr cinfo) { + JPEGErrorManager* myerr = static_cast<JPEGErrorManager*>(cinfo->err); + char buffer[JMSG_LENGTH_MAX]; + (*cinfo->err->format_message)(cinfo, buffer); + kdWarning() << k_funcinfo << buffer << endl; + longjmp(myerr->jmp_buffer, 1); + } +}; + +} // namespace + +#endif /* JPEGERRORMANAGER_H */ diff --git a/src/imageutils/jpegint.h b/src/imageutils/jpegint.h new file mode 100644 index 0000000..95b00d4 --- /dev/null +++ b/src/imageutils/jpegint.h @@ -0,0 +1,392 @@ +/* + * jpegint.h + * + * Copyright (C) 1991-1997, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file provides common declarations for the various JPEG modules. + * These declarations are considered internal to the JPEG library; most + * applications using the library shouldn't need to include this file. + */ + + +/* Declarations for both compression & decompression */ + +typedef enum { /* Operating modes for buffer controllers */ + JBUF_PASS_THRU, /* Plain stripwise operation */ + /* Remaining modes require a full-image buffer to have been created */ + JBUF_SAVE_SOURCE, /* Run source subobject only, save output */ + JBUF_CRANK_DEST, /* Run dest subobject only, using saved data */ + JBUF_SAVE_AND_PASS /* Run both subobjects, save output */ +} J_BUF_MODE; + +/* Values of global_state field (jdapi.c has some dependencies on ordering!) */ +#define CSTATE_START 100 /* after create_compress */ +#define CSTATE_SCANNING 101 /* start_compress done, write_scanlines OK */ +#define CSTATE_RAW_OK 102 /* start_compress done, write_raw_data OK */ +#define CSTATE_WRCOEFS 103 /* jpeg_write_coefficients done */ +#define DSTATE_START 200 /* after create_decompress */ +#define DSTATE_INHEADER 201 /* reading header markers, no SOS yet */ +#define DSTATE_READY 202 /* found SOS, ready for start_decompress */ +#define DSTATE_PRELOAD 203 /* reading multiscan file in start_decompress*/ +#define DSTATE_PRESCAN 204 /* performing dummy pass for 2-pass quant */ +#define DSTATE_SCANNING 205 /* start_decompress done, read_scanlines OK */ +#define DSTATE_RAW_OK 206 /* start_decompress done, read_raw_data OK */ +#define DSTATE_BUFIMAGE 207 /* expecting jpeg_start_output */ +#define DSTATE_BUFPOST 208 /* looking for SOS/EOI in jpeg_finish_output */ +#define DSTATE_RDCOEFS 209 /* reading file in jpeg_read_coefficients */ +#define DSTATE_STOPPING 210 /* looking for EOI in jpeg_finish_decompress */ + + +/* Declarations for compression modules */ + +/* Master control module */ +struct jpeg_comp_master { + JMETHOD(void, prepare_for_pass, (j_compress_ptr cinfo)); + JMETHOD(void, pass_startup, (j_compress_ptr cinfo)); + JMETHOD(void, finish_pass, (j_compress_ptr cinfo)); + + /* State variables made visible to other modules */ + boolean call_pass_startup; /* True if pass_startup must be called */ + boolean is_last_pass; /* True during last pass */ +}; + +/* Main buffer control (downsampled-data buffer) */ +struct jpeg_c_main_controller { + JMETHOD(void, start_pass, (j_compress_ptr cinfo, J_BUF_MODE pass_mode)); + JMETHOD(void, process_data, (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JDIMENSION *in_row_ctr, + JDIMENSION in_rows_avail)); +}; + +/* Compression preprocessing (downsampling input buffer control) */ +struct jpeg_c_prep_controller { + JMETHOD(void, start_pass, (j_compress_ptr cinfo, J_BUF_MODE pass_mode)); + JMETHOD(void, pre_process_data, (j_compress_ptr cinfo, + JSAMPARRAY input_buf, + JDIMENSION *in_row_ctr, + JDIMENSION in_rows_avail, + JSAMPIMAGE output_buf, + JDIMENSION *out_row_group_ctr, + JDIMENSION out_row_groups_avail)); +}; + +/* Coefficient buffer control */ +struct jpeg_c_coef_controller { + JMETHOD(void, start_pass, (j_compress_ptr cinfo, J_BUF_MODE pass_mode)); + JMETHOD(boolean, compress_data, (j_compress_ptr cinfo, + JSAMPIMAGE input_buf)); +}; + +/* Colorspace conversion */ +struct jpeg_color_converter { + JMETHOD(void, start_pass, (j_compress_ptr cinfo)); + JMETHOD(void, color_convert, (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPIMAGE output_buf, + JDIMENSION output_row, int num_rows)); +}; + +/* Downsampling */ +struct jpeg_downsampler { + JMETHOD(void, start_pass, (j_compress_ptr cinfo)); + JMETHOD(void, downsample, (j_compress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION in_row_index, + JSAMPIMAGE output_buf, + JDIMENSION out_row_group_index)); + + boolean need_context_rows; /* TRUE if need rows above & below */ +}; + +/* Forward DCT (also controls coefficient quantization) */ +struct jpeg_forward_dct { + JMETHOD(void, start_pass, (j_compress_ptr cinfo)); + /* perhaps this should be an array??? */ + JMETHOD(void, forward_DCT, (j_compress_ptr cinfo, + jpeg_component_info * compptr, + JSAMPARRAY sample_data, JBLOCKROW coef_blocks, + JDIMENSION start_row, JDIMENSION start_col, + JDIMENSION num_blocks)); +}; + +/* Entropy encoding */ +struct jpeg_entropy_encoder { + JMETHOD(void, start_pass, (j_compress_ptr cinfo, boolean gather_statistics)); + JMETHOD(boolean, encode_mcu, (j_compress_ptr cinfo, JBLOCKROW *MCU_data)); + JMETHOD(void, finish_pass, (j_compress_ptr cinfo)); +}; + +/* Marker writing */ +struct jpeg_marker_writer { + JMETHOD(void, write_file_header, (j_compress_ptr cinfo)); + JMETHOD(void, write_frame_header, (j_compress_ptr cinfo)); + JMETHOD(void, write_scan_header, (j_compress_ptr cinfo)); + JMETHOD(void, write_file_trailer, (j_compress_ptr cinfo)); + JMETHOD(void, write_tables_only, (j_compress_ptr cinfo)); + /* These routines are exported to allow insertion of extra markers */ + /* Probably only COM and APPn markers should be written this way */ + JMETHOD(void, write_marker_header, (j_compress_ptr cinfo, int marker, + unsigned int datalen)); + JMETHOD(void, write_marker_byte, (j_compress_ptr cinfo, int val)); +}; + + +/* Declarations for decompression modules */ + +/* Master control module */ +struct jpeg_decomp_master { + JMETHOD(void, prepare_for_output_pass, (j_decompress_ptr cinfo)); + JMETHOD(void, finish_output_pass, (j_decompress_ptr cinfo)); + + /* State variables made visible to other modules */ + boolean is_dummy_pass; /* True during 1st pass for 2-pass quant */ +}; + +/* Input control module */ +struct jpeg_input_controller { + JMETHOD(int, consume_input, (j_decompress_ptr cinfo)); + JMETHOD(void, reset_input_controller, (j_decompress_ptr cinfo)); + JMETHOD(void, start_input_pass, (j_decompress_ptr cinfo)); + JMETHOD(void, finish_input_pass, (j_decompress_ptr cinfo)); + + /* State variables made visible to other modules */ + boolean has_multiple_scans; /* True if file has multiple scans */ + boolean eoi_reached; /* True when EOI has been consumed */ +}; + +/* Main buffer control (downsampled-data buffer) */ +struct jpeg_d_main_controller { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo, J_BUF_MODE pass_mode)); + JMETHOD(void, process_data, (j_decompress_ptr cinfo, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail)); +}; + +/* Coefficient buffer control */ +struct jpeg_d_coef_controller { + JMETHOD(void, start_input_pass, (j_decompress_ptr cinfo)); + JMETHOD(int, consume_data, (j_decompress_ptr cinfo)); + JMETHOD(void, start_output_pass, (j_decompress_ptr cinfo)); + JMETHOD(int, decompress_data, (j_decompress_ptr cinfo, + JSAMPIMAGE output_buf)); + /* Pointer to array of coefficient virtual arrays, or NULL if none */ + jvirt_barray_ptr *coef_arrays; +}; + +/* Decompression postprocessing (color quantization buffer control) */ +struct jpeg_d_post_controller { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo, J_BUF_MODE pass_mode)); + JMETHOD(void, post_process_data, (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, + JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, + JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail)); +}; + +/* Marker reading & parsing */ +struct jpeg_marker_reader { + JMETHOD(void, reset_marker_reader, (j_decompress_ptr cinfo)); + /* Read markers until SOS or EOI. + * Returns same codes as are defined for jpeg_consume_input: + * JPEG_SUSPENDED, JPEG_REACHED_SOS, or JPEG_REACHED_EOI. + */ + JMETHOD(int, read_markers, (j_decompress_ptr cinfo)); + /* Read a restart marker --- exported for use by entropy decoder only */ + jpeg_marker_parser_method read_restart_marker; + + /* State of marker reader --- nominally internal, but applications + * supplying COM or APPn handlers might like to know the state. + */ + boolean saw_SOI; /* found SOI? */ + boolean saw_SOF; /* found SOF? */ + int next_restart_num; /* next restart number expected (0-7) */ + unsigned int discarded_bytes; /* # of bytes skipped looking for a marker */ +}; + +/* Entropy decoding */ +struct jpeg_entropy_decoder { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo)); + JMETHOD(boolean, decode_mcu, (j_decompress_ptr cinfo, + JBLOCKROW *MCU_data)); + + /* This is here to share code between baseline and progressive decoders; */ + /* other modules probably should not use it */ + boolean insufficient_data; /* set TRUE after emitting warning */ +}; + +/* Inverse DCT (also performs dequantization) */ +typedef JMETHOD(void, inverse_DCT_method_ptr, + (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col)); + +struct jpeg_inverse_dct { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo)); + /* It is useful to allow each component to have a separate IDCT method. */ + inverse_DCT_method_ptr inverse_DCT[MAX_COMPONENTS]; +}; + +/* Upsampling (note that upsampler must also call color converter) */ +struct jpeg_upsampler { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo)); + JMETHOD(void, upsample, (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, + JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, + JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail)); + + boolean need_context_rows; /* TRUE if need rows above & below */ +}; + +/* Colorspace conversion */ +struct jpeg_color_deconverter { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo)); + JMETHOD(void, color_convert, (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION input_row, + JSAMPARRAY output_buf, int num_rows)); +}; + +/* Color quantization or color precision reduction */ +struct jpeg_color_quantizer { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo, boolean is_pre_scan)); + JMETHOD(void, color_quantize, (j_decompress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPARRAY output_buf, + int num_rows)); + JMETHOD(void, finish_pass, (j_decompress_ptr cinfo)); + JMETHOD(void, new_color_map, (j_decompress_ptr cinfo)); +}; + + +/* Miscellaneous useful macros */ + +#undef MAX +#define MAX(a,b) ((a) > (b) ? (a) : (b)) +#undef MIN +#define MIN(a,b) ((a) < (b) ? (a) : (b)) + + +/* We assume that right shift corresponds to signed division by 2 with + * rounding towards minus infinity. This is correct for typical "arithmetic + * shift" instructions that shift in copies of the sign bit. But some + * C compilers implement >> with an unsigned shift. For these machines you + * must define RIGHT_SHIFT_IS_UNSIGNED. + * RIGHT_SHIFT provides a proper signed right shift of an INT32 quantity. + * It is only applied with constant shift counts. SHIFT_TEMPS must be + * included in the variables of any routine using RIGHT_SHIFT. + */ + +#ifdef RIGHT_SHIFT_IS_UNSIGNED +#define SHIFT_TEMPS INT32 shift_temp; +#define RIGHT_SHIFT(x,shft) \ + ((shift_temp = (x)) < 0 ? \ + (shift_temp >> (shft)) | ((~((INT32) 0)) << (32-(shft))) : \ + (shift_temp >> (shft))) +#else +#define SHIFT_TEMPS +#define RIGHT_SHIFT(x,shft) ((x) >> (shft)) +#endif + + +/* Short forms of external names for systems with brain-damaged linkers. */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jinit_compress_master jICompress +#define jinit_c_master_control jICMaster +#define jinit_c_main_controller jICMainC +#define jinit_c_prep_controller jICPrepC +#define jinit_c_coef_controller jICCoefC +#define jinit_color_converter jICColor +#define jinit_downsampler jIDownsampler +#define jinit_forward_dct jIFDCT +#define jinit_huff_encoder jIHEncoder +#define jinit_phuff_encoder jIPHEncoder +#define jinit_marker_writer jIMWriter +#define jinit_master_decompress jIDMaster +#define jinit_d_main_controller jIDMainC +#define jinit_d_coef_controller jIDCoefC +#define jinit_d_post_controller jIDPostC +#define jinit_input_controller jIInCtlr +#define jinit_marker_reader jIMReader +#define jinit_huff_decoder jIHDecoder +#define jinit_phuff_decoder jIPHDecoder +#define jinit_inverse_dct jIIDCT +#define jinit_upsampler jIUpsampler +#define jinit_color_deconverter jIDColor +#define jinit_1pass_quantizer jI1Quant +#define jinit_2pass_quantizer jI2Quant +#define jinit_merged_upsampler jIMUpsampler +#define jinit_memory_mgr jIMemMgr +#define jdiv_round_up jDivRound +#define jround_up jRound +#define jcopy_sample_rows jCopySamples +#define jcopy_block_row jCopyBlocks +#define jzero_far jZeroFar +#define jpeg_zigzag_order jZIGTable +#define jpeg_natural_order jZAGTable +#endif /* NEED_SHORT_EXTERNAL_NAMES */ + + +/* Compression module initialization routines */ +EXTERN(void) jinit_compress_master JPP((j_compress_ptr cinfo)); +EXTERN(void) jinit_c_master_control JPP((j_compress_ptr cinfo, + boolean transcode_only)); +EXTERN(void) jinit_c_main_controller JPP((j_compress_ptr cinfo, + boolean need_full_buffer)); +EXTERN(void) jinit_c_prep_controller JPP((j_compress_ptr cinfo, + boolean need_full_buffer)); +EXTERN(void) jinit_c_coef_controller JPP((j_compress_ptr cinfo, + boolean need_full_buffer)); +EXTERN(void) jinit_color_converter JPP((j_compress_ptr cinfo)); +EXTERN(void) jinit_downsampler JPP((j_compress_ptr cinfo)); +EXTERN(void) jinit_forward_dct JPP((j_compress_ptr cinfo)); +EXTERN(void) jinit_huff_encoder JPP((j_compress_ptr cinfo)); +EXTERN(void) jinit_phuff_encoder JPP((j_compress_ptr cinfo)); +EXTERN(void) jinit_marker_writer JPP((j_compress_ptr cinfo)); +/* Decompression module initialization routines */ +EXTERN(void) jinit_master_decompress JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_d_main_controller JPP((j_decompress_ptr cinfo, + boolean need_full_buffer)); +EXTERN(void) jinit_d_coef_controller JPP((j_decompress_ptr cinfo, + boolean need_full_buffer)); +EXTERN(void) jinit_d_post_controller JPP((j_decompress_ptr cinfo, + boolean need_full_buffer)); +EXTERN(void) jinit_input_controller JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_marker_reader JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_huff_decoder JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_phuff_decoder JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_inverse_dct JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_upsampler JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_color_deconverter JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_1pass_quantizer JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_2pass_quantizer JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_merged_upsampler JPP((j_decompress_ptr cinfo)); +/* Memory manager initialization */ +EXTERN(void) jinit_memory_mgr JPP((j_common_ptr cinfo)); + +/* Utility routines in jutils.c */ +EXTERN(long) jdiv_round_up JPP((long a, long b)); +EXTERN(long) jround_up JPP((long a, long b)); +EXTERN(void) jcopy_sample_rows JPP((JSAMPARRAY input_array, int source_row, + JSAMPARRAY output_array, int dest_row, + int num_rows, JDIMENSION num_cols)); +EXTERN(void) jcopy_block_row JPP((JBLOCKROW input_row, JBLOCKROW output_row, + JDIMENSION num_blocks)); +EXTERN(void) jzero_far JPP((void FAR * target, size_t bytestozero)); +/* Constant tables in jutils.c */ +#if 0 /* This table is not actually needed in v6a */ +extern const int jpeg_zigzag_order[]; /* natural coef order to zigzag order */ +#endif +extern const int jpeg_natural_order[]; /* zigzag coef order to natural order */ + +/* Suppress undefined-structure complaints if necessary. */ + +#ifdef INCOMPLETE_TYPES_BROKEN +#ifndef AM_MEMORY_MANAGER /* only jmemmgr.c defines these */ +struct jvirt_sarray_control { long dummy; }; +struct jvirt_barray_control { long dummy; }; +#endif +#endif /* INCOMPLETE_TYPES_BROKEN */ diff --git a/src/imageutils/orient6.jpg b/src/imageutils/orient6.jpg Binary files differnew file mode 100644 index 0000000..db1002b --- /dev/null +++ b/src/imageutils/orient6.jpg diff --git a/src/imageutils/orientation.h b/src/imageutils/orientation.h new file mode 100644 index 0000000..64f0ad8 --- /dev/null +++ b/src/imageutils/orientation.h @@ -0,0 +1,58 @@ +// vim: set tabstop=4 shiftwidth=4 noexpandtab +/* +Gwenview - A simple image viewer for KDE +Copyright 2000-2004 Aurélien Gâteau + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +#ifndef ORIENTATION_H +#define ORIENTATION_H + + +namespace ImageUtils { + +/* Explanation extracted from http://sylvana.net/jpegcrop/exif_orientation.html + + For convenience, here is what the letter F would look like if it were tagged +correctly and displayed by a program that ignores the orientation tag (thus +showing the stored image): + + 1 2 3 4 5 6 7 8 + +888888 888888 88 88 8888888888 88 88 8888888888 +88 88 88 88 88 88 88 88 88 88 88 88 +8888 8888 8888 8888 88 8888888888 8888888888 88 +88 88 88 88 +88 88 888888 888888 + +*/ + +enum Orientation { + NOT_AVAILABLE=0, + NORMAL=1, + HFLIP=2, + ROT_180=3, + VFLIP=4, + TRANSPOSE=5, + ROT_90=6, + TRANSVERSE=7, + ROT_270=8 +}; + +} + + +#endif diff --git a/src/imageutils/scale.cpp b/src/imageutils/scale.cpp new file mode 100644 index 0000000..99ed186 --- /dev/null +++ b/src/imageutils/scale.cpp @@ -0,0 +1,1979 @@ +// This file includes code for scaling images, in two versions. +// One ported from ImageMagick (slower, but can achieve better quality), +// and from Imlib2 ported by Mosfet (very fast). + + +// ImageMagick code begin +// ---------------------- + +// This code is ImageMagick's resize code, adapted for QImage, with +// fastfloat class added as an optimization. +// The original license text follows. + +/* +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% % +% % +% RRRR EEEEE SSSSS IIIII ZZZZZ EEEEE % +% R R E SS I ZZ E % +% RRRR EEE SSS I ZZZ EEE % +% R R E SS I ZZ E % +% R R EEEEE SSSSS IIIII ZZZZZ EEEEE % +% % +% ImageMagick Image Resize Methods % +% % +% % +% Software Design % +% John Cristy % +% July 1992 % +% % +% % +% Copyright (C) 2003 ImageMagick Studio, a non-profit organization dedicated % +% to making software imaging solutions freely available. % +% % +% Permission is hereby granted, free of charge, to any person obtaining a % +% copy of this software and associated documentation files ("ImageMagick"), % +% to deal in ImageMagick without restriction, including without limitation % +% the rights to use, copy, modify, merge, publish, distribute, sublicense, % +% and/or sell copies of ImageMagick, and to permit persons to whom the % +% ImageMagick is furnished to do so, subject to the following conditions: % +% % +% The above copyright notice and this permission notice shall be included in % +% all copies or substantial portions of ImageMagick. % +% % +% The software is provided "as is", without warranty of any kind, express or % +% implied, including but not limited to the warranties of merchantability, % +% fitness for a particular purpose and noninfringement. In no event shall % +% ImageMagick Studio be liable for any claim, damages or other liability, % +% whether in an action of contract, tort or otherwise, arising from, out of % +% or in connection with ImageMagick or the use or other dealings in % +% ImageMagick. % +% % +% Except as contained in this notice, the name of the ImageMagick Studio % +% shall not be used in advertising or otherwise to promote the sale, use or % +% other dealings in ImageMagick without prior written authorization from the % +% ImageMagick Studio. % +% % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% +*/ +#include "config.h" + +// System +#ifdef HAVE_ENDIAN_H +#include <endian.h> +#else +#ifdef HAVE_SYS_ENDIAN_H +#include <sys/endian.h> +#endif +#endif + +// Qt +#include <qimage.h> +#include <qcolor.h> + +#include <kdeversion.h> +#include <kcpuinfo.h> + +#include <string.h> +#include <stdlib.h> + +// Local +#include "imageutils.h" + +// everything in namespace +namespace ImageUtils { + + +#define Max QMAX +#define Min QMIN + +// mustn't be less than used precision (i.e. 1/fastfloat::RATIO) +#define MagickEpsilon 0.0002 + +// fastfloat begin +// this class stores floating point numbers as integers, with BITS shift, +// i.e. value XYZ is stored as XYZ * RATIO +struct fastfloat + { + private: + enum { BITS = 12, RATIO = 4096 }; + public: + fastfloat() {} + fastfloat( long v ) : value( v << BITS ) {} + fastfloat( int v ) : value( v << BITS ) {} + fastfloat( double v ) : value( static_cast< long >( v * RATIO + 0.5 )) {} + double toDouble() const { return static_cast< double >( value ) / RATIO; } + long toLong() const { return value >> BITS; } + fastfloat& operator += ( fastfloat r ) { value += r.value; return *this; } + fastfloat& operator -= ( fastfloat r ) { value -= r.value; return *this; } + fastfloat& operator *= ( fastfloat r ) { value = static_cast< long long >( value ) * r.value >> BITS; return *this; } + fastfloat& operator /= ( fastfloat r ) { value = ( static_cast< long long >( value ) << BITS ) / r.value; return *this; } + bool operator< ( fastfloat r ) const { return value < r.value; } + bool operator<= ( fastfloat r ) const { return value <= r.value; } + bool operator> ( fastfloat r ) const { return value > r.value; } + bool operator>= ( fastfloat r ) const { return value >= r.value; } + bool operator== ( fastfloat r ) const { return value == r.value; } + bool operator!= ( fastfloat r ) const { return value != r.value; } + fastfloat operator-() const { return fastfloat( -value, false ); } + private: + fastfloat( long v, bool ) : value( v ) {} // for operator-() + long value; + }; + +inline fastfloat operator+ ( fastfloat l, fastfloat r ) { return fastfloat( l ) += r; } +inline fastfloat operator- ( fastfloat l, fastfloat r ) { return fastfloat( l ) -= r; } +inline fastfloat operator* ( fastfloat l, fastfloat r ) { return fastfloat( l ) *= r; } +inline fastfloat operator/ ( fastfloat l, fastfloat r ) { return fastfloat( l ) /= r; } + +inline bool operator< ( fastfloat l, double r ) { return l < fastfloat( r ); } +inline bool operator<= ( fastfloat l, double r ) { return l <= fastfloat( r ); } +inline bool operator> ( fastfloat l, double r ) { return l > fastfloat( r ); } +inline bool operator>= ( fastfloat l, double r ) { return l >= fastfloat( r ); } +inline bool operator== ( fastfloat l, double r ) { return l == fastfloat( r ); } +inline bool operator!= ( fastfloat l, double r ) { return l != fastfloat( r ); } + +inline bool operator< ( double l, fastfloat r ) { return fastfloat( l ) < r ; } +inline bool operator<= ( double l, fastfloat r ) { return fastfloat( l ) <= r ; } +inline bool operator> ( double l, fastfloat r ) { return fastfloat( l ) > r ; } +inline bool operator>= ( double l, fastfloat r ) { return fastfloat( l ) >= r ; } +inline bool operator== ( double l, fastfloat r ) { return fastfloat( l ) == r ; } +inline bool operator!= ( double l, fastfloat r ) { return fastfloat( l ) != r ; } + +inline double fasttodouble( fastfloat v ) { return v.toDouble(); } +inline long fasttolong( fastfloat v ) { return v.toLong(); } + +#if 1 // change to 0 to turn fastfloat usage off +#else +#define fastfloat double +#define fasttodouble( v ) double( v ) +#define fasttolong( v ) long( v ) +#endif + +//fastfloat end + + +typedef fastfloat (*Filter)(const fastfloat, const fastfloat); + +typedef struct _ContributionInfo +{ + fastfloat + weight; + + long + pixel; +} ContributionInfo; + + +/* +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% % +% % +% % +% R e s i z e I m a g e % +% % +% % +% % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% ResizeImage() scales an image to the desired dimensions with one of these +% filters: +% +% Bessel Blackman Box +% Catrom Cubic Gaussian +% Hanning Hermite Lanczos +% Mitchell Point Quandratic +% Sinc Triangle +% +% Most of the filters are FIR (finite impulse response), however, Bessel, +% Gaussian, and Sinc are IIR (infinite impulse response). Bessel and Sinc +% are windowed (brought down to zero) with the Blackman filter. +% +% ResizeImage() was inspired by Paul Heckbert's zoom program. +% +% The format of the ResizeImage method is: +% +% Image *ResizeImage(Image *image,const unsigned long columns, +% const unsigned long rows,const FilterTypes filter,const double blur, +% ExceptionInfo *exception) +% +% A description of each parameter follows: +% +% o image: The image. +% +% o columns: The number of columns in the scaled image. +% +% o rows: The number of rows in the scaled image. +% +% o filter: Image filter to use. +% +% o blur: The blur factor where > 1 is blurry, < 1 is sharp. +% +% o exception: Return any errors or warnings in this structure. +% +% +*/ + +#if 0 +static fastfloat Bessel(const fastfloat x,const fastfloat) +{ + if (x == 0.0) + return(MagickPI/4.0); + return(BesselOrderOne(MagickPI*x)/(2.0*x)); +} + +static fastfloat Sinc(const fastfloat x,const fastfloat) +{ + if (x == 0.0) + return(1.0); + return(sin(MagickPI*x)/(MagickPI*x)); +} + +static fastfloat Blackman(const fastfloat x,const fastfloat) +{ + return(0.42+0.5*cos(MagickPI*x)+0.08*cos(2*MagickPI*x)); +} + +static fastfloat BlackmanBessel(const fastfloat x,const fastfloat) +{ + return(Blackman(x/support,support)*Bessel(x,support)); +} + +static fastfloat BlackmanSinc(const fastfloat x,const fastfloat) +{ + return(Blackman(x/support,support)*Sinc(x,support)); +} +#endif + +static fastfloat Box(const fastfloat x,const fastfloat) +{ + if (x < -0.5) + return(0.0); + if (x < 0.5) + return(1.0); + return(0.0); +} + +#if 0 +static fastfloat Catrom(const fastfloat x,const fastfloat) +{ + if (x < -2.0) + return(0.0); + if (x < -1.0) + return(0.5*(4.0+x*(8.0+x*(5.0+x)))); + if (x < 0.0) + return(0.5*(2.0+x*x*(-5.0-3.0*x))); + if (x < 1.0) + return(0.5*(2.0+x*x*(-5.0+3.0*x))); + if (x < 2.0) + return(0.5*(4.0+x*(-8.0+x*(5.0-x)))); + return(0.0); +} + +static fastfloat Cubic(const fastfloat x,const fastfloat) +{ + if (x < -2.0) + return(0.0); + if (x < -1.0) + return((2.0+x)*(2.0+x)*(2.0+x)/6.0); + if (x < 0.0) + return((4.0+x*x*(-6.0-3.0*x))/6.0); + if (x < 1.0) + return((4.0+x*x*(-6.0+3.0*x))/6.0); + if (x < 2.0) + return((2.0-x)*(2.0-x)*(2.0-x)/6.0); + return(0.0); +} + +static fastfloat Gaussian(const fastfloat x,const fastfloat) +{ + return(exp(-2.0*x*x)*sqrt(2.0/MagickPI)); +} + +static fastfloat Hanning(const fastfloat x,const fastfloat) +{ + return(0.5+0.5*cos(MagickPI*x)); +} + +static fastfloat Hamming(const fastfloat x,const fastfloat) +{ + return(0.54+0.46*cos(MagickPI*x)); +} + +static fastfloat Hermite(const fastfloat x,const fastfloat) +{ + if (x < -1.0) + return(0.0); + if (x < 0.0) + return((2.0*(-x)-3.0)*(-x)*(-x)+1.0); + if (x < 1.0) + return((2.0*x-3.0)*x*x+1.0); + return(0.0); +} + +static fastfloat Lanczos(const fastfloat x,const fastfloat support) +{ + if (x < -3.0) + return(0.0); + if (x < 0.0) + return(Sinc(-x,support)*Sinc(-x/3.0,support)); + if (x < 3.0) + return(Sinc(x,support)*Sinc(x/3.0,support)); + return(0.0); +} + +static fastfloat Mitchell(const fastfloat x,const fastfloat) +{ +#define B (1.0/3.0) +#define C (1.0/3.0) +#define P0 (( 6.0- 2.0*B )/6.0) +#define P2 ((-18.0+12.0*B+ 6.0*C)/6.0) +#define P3 (( 12.0- 9.0*B- 6.0*C)/6.0) +#define Q0 (( 8.0*B+24.0*C)/6.0) +#define Q1 (( -12.0*B-48.0*C)/6.0) +#define Q2 (( 6.0*B+30.0*C)/6.0) +#define Q3 (( - 1.0*B- 6.0*C)/6.0) + + if (x < -2.0) + return(0.0); + if (x < -1.0) + return(Q0-x*(Q1-x*(Q2-x*Q3))); + if (x < 0.0) + return(P0+x*x*(P2-x*P3)); + if (x < 1.0) + return(P0+x*x*(P2+x*P3)); + if (x < 2.0) + return(Q0+x*(Q1+x*(Q2+x*Q3))); + return(0.0); + +#undef B +#undef C +#undef P0 +#undef P2 +#undef P3 +#undef Q0 +#undef Q1 +#undef Q2 +#undef Q3 +} +#endif + +// this is the same like Mitchell, but it has different values +// for B and C, resulting in sharper images +// http://sourceforge.net/mailarchive/forum.php?thread_id=7445822&forum_id=1210 +static fastfloat Bicubic(const fastfloat x,const fastfloat) +{ +#define B (0.0/3.0) +#define C (2.0/3.0) +#define P0 (( 6.0- 2.0*B )/6.0) +#define P2 ((-18.0+12.0*B+ 6.0*C)/6.0) +#define P3 (( 12.0- 9.0*B- 6.0*C)/6.0) +#define Q0 (( 8.0*B+24.0*C)/6.0) +#define Q1 (( -12.0*B-48.0*C)/6.0) +#define Q2 (( 6.0*B+30.0*C)/6.0) +#define Q3 (( - 1.0*B- 6.0*C)/6.0) + + if (x < -2.0) + return(0.0); + if (x < -1.0) + return(Q0-x*(Q1-x*(Q2-x*Q3))); + if (x < 0.0) + return(P0+x*x*(P2-x*P3)); + if (x < 1.0) + return(P0+x*x*(P2+x*P3)); + if (x < 2.0) + return(Q0+x*(Q1+x*(Q2+x*Q3))); + return(0.0); + +#undef B +#undef C +#undef P0 +#undef P2 +#undef P3 +#undef Q0 +#undef Q1 +#undef Q2 +#undef Q3 +} + +#if 0 +static fastfloat Quadratic(const fastfloat x,const fastfloat) +{ + if (x < -1.5) + return(0.0); + if (x < -0.5) + return(0.5*(x+1.5)*(x+1.5)); + if (x < 0.5) + return(0.75-x*x); + if (x < 1.5) + return(0.5*(x-1.5)*(x-1.5)); + return(0.0); +} +#endif + +static fastfloat Triangle(const fastfloat x,const fastfloat) +{ + if (x < -1.0) + return(0.0); + if (x < 0.0) + return(1.0+x); + if (x < 1.0) + return(1.0-x); + return(0.0); +} + +static void HorizontalFilter(const QImage& source,QImage& destination, + const fastfloat x_factor,const fastfloat blur, + ContributionInfo *contribution, Filter filter, fastfloat filtersupport) +{ + fastfloat + center, + density, + scale, + support; + + long + n, + start, + stop, + y; + + register long + i, + x; + + /* + Apply filter to resize horizontally from source to destination. + */ + scale=blur*Max(1.0/x_factor,1.0); + support=scale* filtersupport; + if (support <= 0.5) + { + /* + Reduce to point sampling. + */ + support=0.5+MagickEpsilon; + scale=1.0; + } + scale=1.0/scale; + for (x=0; x < (long) destination.width(); x++) + { + center=(fastfloat) (x+0.5)/x_factor; + start= fasttolong(Max(center-support+0.5,0)); + stop= fasttolong(Min(center+support+0.5,source.width())); + density=0.0; + for (n=0; n < (stop-start); n++) + { + contribution[n].pixel=start+n; + contribution[n].weight= + filter (scale*(start+n-center+0.5), filtersupport ); + density+=contribution[n].weight; + } + if ((density != 0.0) && (density != 1.0)) + { + /* + Normalize. + */ + density=1.0/density; + for (i=0; i < n; i++) + contribution[i].weight*=density; + } +// p=AcquireImagePixels(source,contribution[0].pixel,0,contribution[n-1].pixel- +// contribution[0].pixel+1,source->rows,exception); +// q=SetImagePixels(destination,x,0,1,destination->rows); + for (y=0; y < (long) destination.height(); y++) + { + fastfloat red = 0; + fastfloat green = 0; + fastfloat blue = 0; + fastfloat alpha = 0; + for (i=0; i < n; i++) + { + int px = contribution[i].pixel; + int py = y; + QRgb p = reinterpret_cast< QRgb* >( source.jumpTable()[ py ])[ px ]; + red+=contribution[i].weight*qRed(p); + green+=contribution[i].weight*qGreen(p); + blue+=contribution[i].weight*qBlue(p); + alpha+=contribution[i].weight*qAlpha(p); + } + QRgb pix = qRgba( + fasttolong( red < 0 ? 0 : red > 255 ? 255 : red + 0.5 ), + fasttolong( green < 0 ? 0 : green > 255 ? 255 : green + 0.5 ), + fasttolong( blue < 0 ? 0 : blue > 255 ? 255 : blue + 0.5 ), + fasttolong( alpha < 0 ? 0 : alpha > 255 ? 255 : alpha + 0.5 )); + reinterpret_cast< QRgb* >( destination.jumpTable()[ y ])[ x ] = pix; + } + } +} + +static void VerticalFilter(const QImage& source,QImage& destination, + const fastfloat y_factor,const fastfloat blur, + ContributionInfo *contribution, Filter filter, fastfloat filtersupport ) +{ + fastfloat + center, + density, + scale, + support; + + long + n, + start, + stop, + x; + + register long + i, + y; + + /* + Apply filter to resize vertically from source to destination. + */ + scale=blur*Max(1.0/y_factor,1.0); + support=scale* filtersupport; + if (support <= 0.5) + { + /* + Reduce to point sampling. + */ + support=0.5+MagickEpsilon; + scale=1.0; + } + scale=1.0/scale; + for (y=0; y < (long) destination.height(); y++) + { + center=(fastfloat) (y+0.5)/y_factor; + start= fasttolong(Max(center-support+0.5,0)); + stop= fasttolong(Min(center+support+0.5,source.height())); + density=0.0; + for (n=0; n < (stop-start); n++) + { + contribution[n].pixel=start+n; + contribution[n].weight= + filter (scale*(start+n-center+0.5), filtersupport); + density+=contribution[n].weight; + } + if ((density != 0.0) && (density != 1.0)) + { + /* + Normalize. + */ + density=1.0/density; + for (i=0; i < n; i++) + contribution[i].weight*=density; + } +// p=AcquireImagePixels(source,0,contribution[0].pixel,source->columns, +// contribution[n-1].pixel-contribution[0].pixel+1,exception); +// q=SetImagePixels(destination,0,y,destination->columns,1); + for (x=0; x < (long) destination.width(); x++) + { + fastfloat red = 0; + fastfloat green = 0; + fastfloat blue = 0; + fastfloat alpha = 0; + for (i=0; i < n; i++) + { + int px = x; + int py = contribution[i].pixel; + QRgb p = reinterpret_cast< QRgb* >( source.jumpTable()[ py ])[ px ]; + red+=contribution[i].weight*qRed(p); + green+=contribution[i].weight*qGreen(p); + blue+=contribution[i].weight*qBlue(p); + alpha+=contribution[i].weight*qAlpha(p); + } + QRgb pix = qRgba( + fasttolong( red < 0 ? 0 : red > 255 ? 255 : red + 0.5 ), + fasttolong( green < 0 ? 0 : green > 255 ? 255 : green + 0.5 ), + fasttolong( blue < 0 ? 0 : blue > 255 ? 255 : blue + 0.5 ), + fasttolong( alpha < 0 ? 0 : alpha > 255 ? 255 : alpha + 0.5 )); + reinterpret_cast< QRgb* >( destination.jumpTable()[ y ])[ x ] = pix; + } + } +} + +static QImage ResizeImage(const QImage& image,const int columns, + const int rows, Filter filter, fastfloat filtersupport, double blur) +{ + ContributionInfo + *contribution; + + fastfloat + support, + x_factor, + x_support, + y_factor, + y_support; + + /* + Initialize resize image attributes. + */ + if ((columns == image.width()) && (rows == image.height()) && (blur == 1.0)) + return image.copy(); + QImage resize_image( columns, rows, 32 ); + resize_image.setAlphaBuffer( image.hasAlphaBuffer()); + /* + Allocate filter contribution info. + */ + x_factor=(fastfloat) resize_image.width()/image.width(); + y_factor=(fastfloat) resize_image.height()/image.height(); +// i=(long) LanczosFilter; +// if (image->filter != UndefinedFilter) +// i=(long) image->filter; +// else +// if ((image->storage_class == PseudoClass) || image->matte || +// ((x_factor*y_factor) > 1.0)) +// i=(long) MitchellFilter; + x_support=blur*Max(1.0/x_factor,1.0)*filtersupport; + y_support=blur*Max(1.0/y_factor,1.0)*filtersupport; + support=Max(x_support,y_support); + if (support < filtersupport) + support=filtersupport; + contribution=new ContributionInfo[ fasttolong( 2.0*Max(support,0.5)+3 ) ]; + Q_CHECK_PTR( contribution ); + /* + Resize image. + */ + if (((fastfloat) columns*(image.height()+rows)) > + ((fastfloat) rows*(image.width()+columns))) + { + QImage source_image( columns, image.height(), 32 ); + source_image.setAlphaBuffer( image.hasAlphaBuffer()); + HorizontalFilter(image,source_image,x_factor,blur, + contribution,filter,filtersupport); + VerticalFilter(source_image,resize_image,y_factor, + blur,contribution,filter,filtersupport); + } + else + { + QImage source_image( image.width(), rows, 32 ); + source_image.setAlphaBuffer( image.hasAlphaBuffer()); + VerticalFilter(image,source_image,y_factor,blur, + contribution,filter,filtersupport); + HorizontalFilter(source_image,resize_image,x_factor, + blur,contribution,filter,filtersupport); + } + /* + Free allocated memory. + */ + delete[] contribution; + return(resize_image); +} + + +#undef Max +#undef Min +#undef MagickEpsilon + + +// filters and their matching support values +#if 0 + static const FilterInfo + filters[SincFilter+1] = + { + { Box, 0.0 }, + { Box, 0.0 }, + { Box, 0.5 }, + { Triangle, 1.0 }, + { Hermite, 1.0 }, + { Hanning, 1.0 }, + { Hamming, 1.0 }, + { Blackman, 1.0 }, + { Gaussian, 1.25 }, + { Quadratic, 1.5 }, + { Cubic, 2.0 }, + { Catrom, 2.0 }, + { Mitchell, 2.0 }, + { Lanczos, 3.0 }, + { BlackmanBessel, 3.2383 }, + { BlackmanSinc, 4.0 } + }; +#endif + + +/* +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% % +% % +% % +% S a m p l e I m a g e % +% % +% % +% % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% SampleImage() scales an image to the desired dimensions with pixel +% sampling. Unlike other scaling methods, this method does not introduce +% any additional color into the scaled image. +% +% The format of the SampleImage method is: +% +% Image *SampleImage(const Image *image,const unsigned long columns, +% const unsigned long rows,ExceptionInfo *exception) +% +% A description of each parameter follows: +% +% o image: The image. +% +% o columns: The number of columns in the sampled image. +% +% o rows: The number of rows in the sampled image. +% +% o exception: Return any errors or warnings in this structure. +% +% +*/ +QImage SampleImage(const QImage& image,const int columns, + const int rows) +{ + int + *x_offset, + *y_offset; + + long + j, + y; + + uchar + *pixels; + + register const uchar + *p; + + register long + x; + + register uchar + *q; + + /* + Initialize sampled image attributes. + */ + if ((columns == image.width()) && (rows == image.height())) + return image; + // This function is modified to handle any image depth, not only + // 32bit like the ImageMagick original. This avoids the relatively + // expensive conversion. + const int d = image.depth() / 8; + QImage sample_image( columns, rows, image.depth()); + sample_image.setAlphaBuffer( image.hasAlphaBuffer()); + /* + Allocate scan line buffer and column offset buffers. + */ + pixels= new uchar[ image.width() * d ]; + x_offset= new int[ sample_image.width() ]; + y_offset= new int[ sample_image.height() ]; + /* + Initialize pixel offsets. + */ +// In the following several code 0.5 needs to be added, otherwise the image +// would be moved by half a pixel to bottom-right, just like +// with Qt's QImage::scale() + for (x=0; x < (long) sample_image.width(); x++) + { + x_offset[x]=int((x+0.5)*image.width()/sample_image.width()); + } + for (y=0; y < (long) sample_image.height(); y++) + { + y_offset[y]=int((y+0.5)*image.height()/sample_image.height()); + } + /* + Sample each row. + */ + j=(-1); + for (y=0; y < (long) sample_image.height(); y++) + { + q= sample_image.scanLine( y ); + if (j != y_offset[y] ) + { + /* + Read a scan line. + */ + j= y_offset[y]; + p= image.scanLine( j ); + (void) memcpy(pixels,p,image.width()*d); + } + /* + Sample each column. + */ + switch( d ) + { + case 1: // 8bit + for (x=0; x < (long) sample_image.width(); x++) + { + *q++=pixels[ x_offset[x] ]; + } + break; + case 4: // 32bit + for (x=0; x < (long) sample_image.width(); x++) + { + *(QRgb*)q=((QRgb*)pixels)[ x_offset[x] ]; + q += d; + } + break; + default: + for (x=0; x < (long) sample_image.width(); x++) + { + memcpy( q, pixels + x_offset[x] * d, d ); + q += d; + } + break; + } + } + if( d != 4 ) // != 32bit + { + sample_image.setNumColors( image.numColors()); + for( int i = 0; i < image.numColors(); ++i ) + sample_image.setColor( i, image.color( i )); + } + delete[] y_offset; + delete[] x_offset; + delete[] pixels; + return sample_image; +} + + +// ImageMagick code end + + +// Imlib2/Mosfet code begin +// ------------------------ + +// This code is Imlib2 code, additionally modified by Mosfet, and with few small +// modifications for Gwenview. The MMX scaling code also belongs to it. + +// The original license texts follow. + +/** + * This is the normal smoothscale method, based on Imlib2's smoothscale. + * + * Originally I took the algorithm used in NetPBM and Qt and added MMX/3dnow + * optimizations. It ran in about 1/2 the time as Qt. Then I ported Imlib's + * C algorithm and it ran at about the same speed as my MMX optimized one... + * Finally I ported Imlib's MMX version and it ran in less than half the + * time as my MMX algorithm, (taking only a quarter of the time Qt does). + * + * Changes include formatting, namespaces and other C++'ings, removal of old + * #ifdef'ed code, and removal of unneeded border calculation code. + * + * Imlib2 is (C) Carsten Haitzler and various contributors. The MMX code + * is by Willem Monsuwe <willem@stack.nl>. All other modifications are + * (C) Daniel M. Duley. + */ + +/* + Copyright (C) 2004 Daniel M. Duley <dan.duley@verizon.net> + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +/* +Copyright (C) 2000 Carsten Haitzler and various contributors (see AUTHORS) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies of the Software and its Copyright notices. In addition publicly +documented acknowledgment must be given that this software has been used if no +source code of this software is made available publicly. This includes +acknowledgments in either Copyright notices, Manuals, Publicity and Marketing +documents or any documentation provided with any product containing this +software. This License does not apply to any software that links to the +libraries provided by this software (statically or dynamically), but only to +the software provided. + +Please see the COPYING.PLAIN for a plain-english explanation of this notice +and it's intent. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +namespace MImageScale{ + typedef struct __mimage_scale_info + { + int *xpoints; + unsigned int **ypoints; + int *xapoints, *yapoints; + int xup_yup; + } MImageScaleInfo; + + unsigned int** mimageCalcYPoints(unsigned int *src, int sow, int sh, + int dh); + int* mimageCalcXPoints(int sw, int dw); + int* mimageCalcApoints(int s, int d, int up); + MImageScaleInfo* mimageFreeScaleInfo(MImageScaleInfo *isi); + MImageScaleInfo *mimageCalcScaleInfo(QImage &img, int sw, int sh, + int dw, int dh, char aa, int sow); + void mimageSampleRGBA(MImageScaleInfo *isi, unsigned int *dest, int dxx, + int dyy, int dx, int dy, int dw, int dh, int dow); + void mimageScaleAARGBA(MImageScaleInfo *isi, unsigned int *dest, int dxx, + int dyy, int dx, int dy, int dw, int dh, int dow, + int sow); + void mimageScaleAARGB(MImageScaleInfo *isi, unsigned int *dest, int dxx, + int dyy, int dx, int dy, int dw, int dh, int dow, int + sow); + QImage smoothScale(const QImage& img, int dw, int dh); + typedef long long llong; +} + +#ifdef HAVE_X86_MMX +extern "C" { + void __mimageScale_mmx_AARGBA(MImageScale::MImageScaleInfo *isi, + unsigned int *dest, int dxx, int dyy, + int dx, int dy, int dw, int dh, + int dow, int sow); +} +#endif + +using namespace MImageScale; + +QImage MImageScale::smoothScale(const QImage& image, int dw, int dh) +{ + QImage img = image.depth() < 32 ? image.convertDepth( 32 ) : image; + int w = img.width(); + int h = img.height(); + + int sow = img.bytesPerLine(); + // handle CroppedQImage + if( img.height() > 1 && sow != img.scanLine( 1 ) - img.scanLine( 0 )) + sow = img.scanLine( 1 ) - img.scanLine( 0 ); + sow = sow / ( img.depth() / 8 ); + + MImageScaleInfo *scaleinfo = + mimageCalcScaleInfo(img, w, h, dw, dh, true, sow); + if(!scaleinfo) + return QImage(); + + QImage buffer(dw, dh, 32); + buffer.setAlphaBuffer(img.hasAlphaBuffer()); + +#ifdef HAVE_X86_MMX +//#warning Using MMX Smoothscale + bool haveMMX = KCPUInfo::haveExtension( KCPUInfo::IntelMMX ); + if(haveMMX){ + __mimageScale_mmx_AARGBA(scaleinfo, (unsigned int *)buffer.scanLine(0), + 0, 0, 0, 0, dw, dh, dw, sow); + } + else +#endif + { + if(img.hasAlphaBuffer()) + mimageScaleAARGBA(scaleinfo, (unsigned int *)buffer.scanLine(0), 0, 0, + 0, 0, dw, dh, dw, sow); + else + mimageScaleAARGB(scaleinfo, (unsigned int *)buffer.scanLine(0), 0, 0, + 0, 0, dw, dh, dw, sow); + } + mimageFreeScaleInfo(scaleinfo); + return(buffer); +} + +// +// Code ported from Imlib... +// + +// FIXME: replace with mRed, etc... These work on pointers to pixels, not +// pixel values +#if BYTE_ORDER == BIG_ENDIAN +#define A_VAL(p) ((unsigned char *)(p))[0] +#define R_VAL(p) ((unsigned char *)(p))[1] +#define G_VAL(p) ((unsigned char *)(p))[2] +#define B_VAL(p) ((unsigned char *)(p))[3] +#elif BYTE_ORDER == LITTLE_ENDIAN +#define A_VAL(p) ((unsigned char *)(p))[3] +#define R_VAL(p) ((unsigned char *)(p))[2] +#define G_VAL(p) ((unsigned char *)(p))[1] +#define B_VAL(p) ((unsigned char *)(p))[0] +#else +#error "BYTE_ORDER is not defined" +#endif + +#define INV_XAP (256 - xapoints[x]) +#define XAP (xapoints[x]) +#define INV_YAP (256 - yapoints[dyy + y]) +#define YAP (yapoints[dyy + y]) + +unsigned int** MImageScale::mimageCalcYPoints(unsigned int *src, + int sow, int sh, int dh) +{ + unsigned int **p; + int i, j = 0; + int rv = 0; + llong val, inc; + + if(dh < 0){ + dh = -dh; + rv = 1; + } + p = new unsigned int* [dh+1]; + + val = 0; + inc = (llong(sh) << 16) / dh; + for(i = 0; i < dh; i++){ + p[j++] = src + ((val >> 16) * sow); + val += inc; + } + if(rv){ + for(i = dh / 2; --i >= 0; ){ + unsigned int *tmp = p[i]; + p[i] = p[dh - i - 1]; + p[dh - i - 1] = tmp; + } + } + return(p); +} + +int* MImageScale::mimageCalcXPoints(int sw, int dw) +{ + int *p, i, j = 0; + int rv = 0; + llong val, inc; + + if(dw < 0){ + dw = -dw; + rv = 1; + } + p = new int[dw+1]; + + val = 0; + inc = (llong(sw) << 16) / dw; + for(i = 0; i < dw; i++){ + p[j++] = (val >> 16); + val += inc; + } + + if(rv){ + for(i = dw / 2; --i >= 0; ){ + int tmp = p[i]; + p[i] = p[dw - i - 1]; + p[dw - i - 1] = tmp; + } + } + return(p); +} + +int* MImageScale::mimageCalcApoints(int s, int d, int up) +{ + int *p, i, j = 0, rv = 0; + + if(d < 0){ + rv = 1; + d = -d; + } + p = new int[d]; + + /* scaling up */ + if(up){ + llong val, inc; + + val = 0; + inc = (llong(s) << 16) / d; + for(i = 0; i < d; i++){ + p[j++] = (val >> 8) - ((val >> 8) & 0xffffff00); + if((val >> 16) >= (s - 1)) + p[j - 1] = 0; + val += inc; + } + } + /* scaling down */ + else{ + llong val, inc; + int ap, Cp; + val = 0; + inc = (llong(s) << 16) / d; + Cp = ((llong(d) << 14) / s) + 1; + for(i = 0; i < d; i++){ + ap = ((0x100 - ((val >> 8) & 0xff)) * Cp) >> 8; + p[j] = ap | (Cp << 16); + j++; + val += inc; + } + } + if(rv){ + int tmp; + for(i = d / 2; --i >= 0; ){ + tmp = p[i]; + p[i] = p[d - i - 1]; + p[d - i - 1] = tmp; + } + } + return(p); +} + +MImageScaleInfo* MImageScale::mimageFreeScaleInfo(MImageScaleInfo *isi) +{ + if(isi){ + delete[] isi->xpoints; + delete[] isi->ypoints; + delete[] isi->xapoints; + delete[] isi->yapoints; + delete isi; + } + return(NULL); +} + +MImageScaleInfo* MImageScale::mimageCalcScaleInfo(QImage &img, int sw, int sh, + int dw, int dh, char aa, int sow) +{ + MImageScaleInfo *isi; + int scw, sch; + + scw = dw * img.width() / sw; + sch = dh * img.height() / sh; + + isi = new MImageScaleInfo; + if(!isi) + return(NULL); + memset(isi, 0, sizeof(MImageScaleInfo)); + + isi->xup_yup = (abs(dw) >= sw) + ((abs(dh) >= sh) << 1); + + isi->xpoints = mimageCalcXPoints(img.width(), scw); + if(!isi->xpoints) + return(mimageFreeScaleInfo(isi)); + isi->ypoints = mimageCalcYPoints((unsigned int *)img.scanLine(0), + sow, img.height(), sch ); + if (!isi->ypoints) + return(mimageFreeScaleInfo(isi)); + if(aa){ + isi->xapoints = mimageCalcApoints(img.width(), scw, isi->xup_yup & 1); + if(!isi->xapoints) + return(mimageFreeScaleInfo(isi)); + isi->yapoints = mimageCalcApoints(img.height(), sch, isi->xup_yup & 2); + if(!isi->yapoints) + return(mimageFreeScaleInfo(isi)); + } + return(isi); +} + +/* scale by pixel sampling only */ +void MImageScale::mimageSampleRGBA(MImageScaleInfo *isi, unsigned int *dest, + int dxx, int dyy, int dx, int dy, int dw, + int dh, int dow) +{ + unsigned int *sptr, *dptr; + int x, y, end; + unsigned int **ypoints = isi->ypoints; + int *xpoints = isi->xpoints; + + /* whats the last pixel ont he line so we stop there */ + end = dxx + dw; + /* go through every scanline in the output buffer */ + for(y = 0; y < dh; y++){ + /* get the pointer to the start of the destination scanline */ + dptr = dest + dx + ((y + dy) * dow); + /* calculate the source line we'll scan from */ + sptr = ypoints[dyy + y]; + /* go thru the scanline and copy across */ + for(x = dxx; x < end; x++) + *dptr++ = sptr[xpoints[x]]; + } +} + +/* FIXME: NEED to optimise ScaleAARGBA - currently its "ok" but needs work*/ + +/* scale by area sampling */ +void MImageScale::mimageScaleAARGBA(MImageScaleInfo *isi, unsigned int *dest, + int dxx, int dyy, int dx, int dy, int dw, + int dh, int dow, int sow) +{ + unsigned int *sptr, *dptr; + int x, y, end; + unsigned int **ypoints = isi->ypoints; + int *xpoints = isi->xpoints; + int *xapoints = isi->xapoints; + int *yapoints = isi->yapoints; + + end = dxx + dw; + /* scaling up both ways */ + if(isi->xup_yup == 3){ + /* go through every scanline in the output buffer */ + for(y = 0; y < dh; y++){ + /* calculate the source line we'll scan from */ + dptr = dest + dx + ((y + dy) * dow); + sptr = ypoints[dyy + y]; + if(YAP > 0){ + for(x = dxx; x < end; x++){ + int r, g, b, a; + int rr, gg, bb, aa; + unsigned int *pix; + + if(XAP > 0){ + pix = ypoints[dyy + y] + xpoints[x]; + r = R_VAL(pix) * INV_XAP; + g = G_VAL(pix) * INV_XAP; + b = B_VAL(pix) * INV_XAP; + a = A_VAL(pix) * INV_XAP; + pix++; + r += R_VAL(pix) * XAP; + g += G_VAL(pix) * XAP; + b += B_VAL(pix) * XAP; + a += A_VAL(pix) * XAP; + pix += sow; + rr = R_VAL(pix) * XAP; + gg = G_VAL(pix) * XAP; + bb = B_VAL(pix) * XAP; + aa = A_VAL(pix) * XAP; + pix--; + rr += R_VAL(pix) * INV_XAP; + gg += G_VAL(pix) * INV_XAP; + bb += B_VAL(pix) * INV_XAP; + aa += A_VAL(pix) * INV_XAP; + r = ((rr * YAP) + (r * INV_YAP)) >> 16; + g = ((gg * YAP) + (g * INV_YAP)) >> 16; + b = ((bb * YAP) + (b * INV_YAP)) >> 16; + a = ((aa * YAP) + (a * INV_YAP)) >> 16; + *dptr++ = qRgba(r, g, b, a); + } + else{ + pix = ypoints[dyy + y] + xpoints[x]; + r = R_VAL(pix) * INV_YAP; + g = G_VAL(pix) * INV_YAP; + b = B_VAL(pix) * INV_YAP; + a = A_VAL(pix) * INV_YAP; + pix += sow; + r += R_VAL(pix) * YAP; + g += G_VAL(pix) * YAP; + b += B_VAL(pix) * YAP; + a += A_VAL(pix) * YAP; + r >>= 8; + g >>= 8; + b >>= 8; + a >>= 8; + *dptr++ = qRgba(r, g, b, a); + } + } + } + else{ + for(x = dxx; x < end; x++){ + int r, g, b, a; + unsigned int *pix; + + if(XAP > 0){ + pix = ypoints[dyy + y] + xpoints[x]; + r = R_VAL(pix) * INV_XAP; + g = G_VAL(pix) * INV_XAP; + b = B_VAL(pix) * INV_XAP; + a = A_VAL(pix) * INV_XAP; + pix++; + r += R_VAL(pix) * XAP; + g += G_VAL(pix) * XAP; + b += B_VAL(pix) * XAP; + a += A_VAL(pix) * XAP; + r >>= 8; + g >>= 8; + b >>= 8; + a >>= 8; + *dptr++ = qRgba(r, g, b, a); + } + else + *dptr++ = sptr[xpoints[x] ]; + } + } + } + } + /* if we're scaling down vertically */ + else if(isi->xup_yup == 1){ + /*\ 'Correct' version, with math units prepared for MMXification \*/ + int Cy, j; + unsigned int *pix; + int r, g, b, a, rr, gg, bb, aa; + int yap; + + /* go through every scanline in the output buffer */ + for(y = 0; y < dh; y++){ + Cy = YAP >> 16; + yap = YAP & 0xffff; + + dptr = dest + dx + ((y + dy) * dow); + for(x = dxx; x < end; x++){ + pix = ypoints[dyy + y] + xpoints[x]; + r = (R_VAL(pix) * yap) >> 10; + g = (G_VAL(pix) * yap) >> 10; + b = (B_VAL(pix) * yap) >> 10; + a = (A_VAL(pix) * yap) >> 10; + for(j = (1 << 14) - yap; j > Cy; j -= Cy){ + pix += sow; + r += (R_VAL(pix) * Cy) >> 10; + g += (G_VAL(pix) * Cy) >> 10; + b += (B_VAL(pix) * Cy) >> 10; + a += (A_VAL(pix) * Cy) >> 10; + } + if(j > 0){ + pix += sow; + r += (R_VAL(pix) * j) >> 10; + g += (G_VAL(pix) * j) >> 10; + b += (B_VAL(pix) * j) >> 10; + a += (A_VAL(pix) * j) >> 10; + } + if(XAP > 0){ + pix = ypoints[dyy + y] + xpoints[x] + 1; + rr = (R_VAL(pix) * yap) >> 10; + gg = (G_VAL(pix) * yap) >> 10; + bb = (B_VAL(pix) * yap) >> 10; + aa = (A_VAL(pix) * yap) >> 10; + for(j = (1 << 14) - yap; j > Cy; j -= Cy){ + pix += sow; + rr += (R_VAL(pix) * Cy) >> 10; + gg += (G_VAL(pix) * Cy) >> 10; + bb += (B_VAL(pix) * Cy) >> 10; + aa += (A_VAL(pix) * Cy) >> 10; + } + if(j > 0){ + pix += sow; + rr += (R_VAL(pix) * j) >> 10; + gg += (G_VAL(pix) * j) >> 10; + bb += (B_VAL(pix) * j) >> 10; + aa += (A_VAL(pix) * j) >> 10; + } + r = r * INV_XAP; + g = g * INV_XAP; + b = b * INV_XAP; + a = a * INV_XAP; + r = (r + ((rr * XAP))) >> 12; + g = (g + ((gg * XAP))) >> 12; + b = (b + ((bb * XAP))) >> 12; + a = (a + ((aa * XAP))) >> 12; + } + else{ + r >>= 4; + g >>= 4; + b >>= 4; + a >>= 4; + } + *dptr = qRgba(r, g, b, a); + dptr++; + } + } + } + /* if we're scaling down horizontally */ + else if(isi->xup_yup == 2){ + /*\ 'Correct' version, with math units prepared for MMXification \*/ + int Cx, j; + unsigned int *pix; + int r, g, b, a, rr, gg, bb, aa; + int xap; + + /* go through every scanline in the output buffer */ + for(y = 0; y < dh; y++){ + dptr = dest + dx + ((y + dy) * dow); + for(x = dxx; x < end; x++){ + Cx = XAP >> 16; + xap = XAP & 0xffff; + + pix = ypoints[dyy + y] + xpoints[x]; + r = (R_VAL(pix) * xap) >> 10; + g = (G_VAL(pix) * xap) >> 10; + b = (B_VAL(pix) * xap) >> 10; + a = (A_VAL(pix) * xap) >> 10; + for(j = (1 << 14) - xap; j > Cx; j -= Cx){ + pix++; + r += (R_VAL(pix) * Cx) >> 10; + g += (G_VAL(pix) * Cx) >> 10; + b += (B_VAL(pix) * Cx) >> 10; + a += (A_VAL(pix) * Cx) >> 10; + } + if(j > 0){ + pix++; + r += (R_VAL(pix) * j) >> 10; + g += (G_VAL(pix) * j) >> 10; + b += (B_VAL(pix) * j) >> 10; + a += (A_VAL(pix) * j) >> 10; + } + if(YAP > 0){ + pix = ypoints[dyy + y] + xpoints[x] + sow; + rr = (R_VAL(pix) * xap) >> 10; + gg = (G_VAL(pix) * xap) >> 10; + bb = (B_VAL(pix) * xap) >> 10; + aa = (A_VAL(pix) * xap) >> 10; + for(j = (1 << 14) - xap; j > Cx; j -= Cx){ + pix++; + rr += (R_VAL(pix) * Cx) >> 10; + gg += (G_VAL(pix) * Cx) >> 10; + bb += (B_VAL(pix) * Cx) >> 10; + aa += (A_VAL(pix) * Cx) >> 10; + } + if(j > 0){ + pix++; + rr += (R_VAL(pix) * j) >> 10; + gg += (G_VAL(pix) * j) >> 10; + bb += (B_VAL(pix) * j) >> 10; + aa += (A_VAL(pix) * j) >> 10; + } + r = r * INV_YAP; + g = g * INV_YAP; + b = b * INV_YAP; + a = a * INV_YAP; + r = (r + ((rr * YAP))) >> 12; + g = (g + ((gg * YAP))) >> 12; + b = (b + ((bb * YAP))) >> 12; + a = (a + ((aa * YAP))) >> 12; + } + else{ + r >>= 4; + g >>= 4; + b >>= 4; + a >>= 4; + } + *dptr = qRgba(r, g, b, a); + dptr++; + } + } + } + /* if we're scaling down horizontally & vertically */ + else{ + /*\ 'Correct' version, with math units prepared for MMXification: + |*| The operation 'b = (b * c) >> 16' translates to pmulhw, + |*| so the operation 'b = (b * c) >> d' would translate to + |*| psllw (16 - d), %mmb; pmulh %mmc, %mmb + \*/ + int Cx, Cy, i, j; + unsigned int *pix; + int a, r, g, b, ax, rx, gx, bx; + int xap, yap; + + for(y = 0; y < dh; y++){ + Cy = YAP >> 16; + yap = YAP & 0xffff; + + dptr = dest + dx + ((y + dy) * dow); + for(x = dxx; x < end; x++){ + Cx = XAP >> 16; + xap = XAP & 0xffff; + + sptr = ypoints[dyy + y] + xpoints[x]; + pix = sptr; + sptr += sow; + rx = (R_VAL(pix) * xap) >> 9; + gx = (G_VAL(pix) * xap) >> 9; + bx = (B_VAL(pix) * xap) >> 9; + ax = (A_VAL(pix) * xap) >> 9; + pix++; + for(i = (1 << 14) - xap; i > Cx; i -= Cx){ + rx += (R_VAL(pix) * Cx) >> 9; + gx += (G_VAL(pix) * Cx) >> 9; + bx += (B_VAL(pix) * Cx) >> 9; + ax += (A_VAL(pix) * Cx) >> 9; + pix++; + } + if(i > 0){ + rx += (R_VAL(pix) * i) >> 9; + gx += (G_VAL(pix) * i) >> 9; + bx += (B_VAL(pix) * i) >> 9; + ax += (A_VAL(pix) * i) >> 9; + } + + r = (rx * yap) >> 14; + g = (gx * yap) >> 14; + b = (bx * yap) >> 14; + a = (ax * yap) >> 14; + + for(j = (1 << 14) - yap; j > Cy; j -= Cy){ + pix = sptr; + sptr += sow; + rx = (R_VAL(pix) * xap) >> 9; + gx = (G_VAL(pix) * xap) >> 9; + bx = (B_VAL(pix) * xap) >> 9; + ax = (A_VAL(pix) * xap) >> 9; + pix++; + for(i = (1 << 14) - xap; i > Cx; i -= Cx){ + rx += (R_VAL(pix) * Cx) >> 9; + gx += (G_VAL(pix) * Cx) >> 9; + bx += (B_VAL(pix) * Cx) >> 9; + ax += (A_VAL(pix) * Cx) >> 9; + pix++; + } + if(i > 0){ + rx += (R_VAL(pix) * i) >> 9; + gx += (G_VAL(pix) * i) >> 9; + bx += (B_VAL(pix) * i) >> 9; + ax += (A_VAL(pix) * i) >> 9; + } + + r += (rx * Cy) >> 14; + g += (gx * Cy) >> 14; + b += (bx * Cy) >> 14; + a += (ax * Cy) >> 14; + } + if(j > 0){ + pix = sptr; + sptr += sow; + rx = (R_VAL(pix) * xap) >> 9; + gx = (G_VAL(pix) * xap) >> 9; + bx = (B_VAL(pix) * xap) >> 9; + ax = (A_VAL(pix) * xap) >> 9; + pix++; + for(i = (1 << 14) - xap; i > Cx; i -= Cx){ + rx += (R_VAL(pix) * Cx) >> 9; + gx += (G_VAL(pix) * Cx) >> 9; + bx += (B_VAL(pix) * Cx) >> 9; + ax += (A_VAL(pix) * Cx) >> 9; + pix++; + } + if(i > 0){ + rx += (R_VAL(pix) * i) >> 9; + gx += (G_VAL(pix) * i) >> 9; + bx += (B_VAL(pix) * i) >> 9; + ax += (A_VAL(pix) * i) >> 9; + } + + r += (rx * j) >> 14; + g += (gx * j) >> 14; + b += (bx * j) >> 14; + a += (ax * j) >> 14; + } + + R_VAL(dptr) = r >> 5; + G_VAL(dptr) = g >> 5; + B_VAL(dptr) = b >> 5; + A_VAL(dptr) = a >> 5; + dptr++; + } + } + } +} + +/* scale by area sampling - IGNORE the ALPHA byte*/ +void MImageScale::mimageScaleAARGB(MImageScaleInfo *isi, unsigned int *dest, + int dxx, int dyy, int dx, int dy, int dw, + int dh, int dow, int sow) +{ + unsigned int *sptr, *dptr; + int x, y, end; + unsigned int **ypoints = isi->ypoints; + int *xpoints = isi->xpoints; + int *xapoints = isi->xapoints; + int *yapoints = isi->yapoints; + + end = dxx + dw; + /* scaling up both ways */ + if(isi->xup_yup == 3){ + /* go through every scanline in the output buffer */ + for(y = 0; y < dh; y++){ + /* calculate the source line we'll scan from */ + dptr = dest + dx + ((y + dy) * dow); + sptr = ypoints[dyy + y]; + if(YAP > 0){ + for(x = dxx; x < end; x++){ + int r = 0, g = 0, b = 0; + int rr = 0, gg = 0, bb = 0; + unsigned int *pix; + + if(XAP > 0){ + pix = ypoints[dyy + y] + xpoints[x]; + r = R_VAL(pix) * INV_XAP; + g = G_VAL(pix) * INV_XAP; + b = B_VAL(pix) * INV_XAP; + pix++; + r += R_VAL(pix) * XAP; + g += G_VAL(pix) * XAP; + b += B_VAL(pix) * XAP; + pix += sow; + rr = R_VAL(pix) * XAP; + gg = G_VAL(pix) * XAP; + bb = B_VAL(pix) * XAP; + pix --; + rr += R_VAL(pix) * INV_XAP; + gg += G_VAL(pix) * INV_XAP; + bb += B_VAL(pix) * INV_XAP; + r = ((rr * YAP) + (r * INV_YAP)) >> 16; + g = ((gg * YAP) + (g * INV_YAP)) >> 16; + b = ((bb * YAP) + (b * INV_YAP)) >> 16; + *dptr++ = qRgba(r, g, b, 0xff); + } + else{ + pix = ypoints[dyy + y] + xpoints[x]; + r = R_VAL(pix) * INV_YAP; + g = G_VAL(pix) * INV_YAP; + b = B_VAL(pix) * INV_YAP; + pix += sow; + r += R_VAL(pix) * YAP; + g += G_VAL(pix) * YAP; + b += B_VAL(pix) * YAP; + r >>= 8; + g >>= 8; + b >>= 8; + *dptr++ = qRgba(r, g, b, 0xff); + } + } + } + else{ + for(x = dxx; x < end; x++){ + int r = 0, g = 0, b = 0; + unsigned int *pix; + + if(XAP > 0){ + pix = ypoints[dyy + y] + xpoints[x]; + r = R_VAL(pix) * INV_XAP; + g = G_VAL(pix) * INV_XAP; + b = B_VAL(pix) * INV_XAP; + pix++; + r += R_VAL(pix) * XAP; + g += G_VAL(pix) * XAP; + b += B_VAL(pix) * XAP; + r >>= 8; + g >>= 8; + b >>= 8; + *dptr++ = qRgba(r, g, b, 0xff); + } + else + *dptr++ = sptr[xpoints[x] ]; + } + } + } + } + /* if we're scaling down vertically */ + else if(isi->xup_yup == 1){ + /*\ 'Correct' version, with math units prepared for MMXification \*/ + int Cy, j; + unsigned int *pix; + int r, g, b, rr, gg, bb; + int yap; + + /* go through every scanline in the output buffer */ + for(y = 0; y < dh; y++){ + Cy = YAP >> 16; + yap = YAP & 0xffff; + + dptr = dest + dx + ((y + dy) * dow); + for(x = dxx; x < end; x++){ + pix = ypoints[dyy + y] + xpoints[x]; + r = (R_VAL(pix) * yap) >> 10; + g = (G_VAL(pix) * yap) >> 10; + b = (B_VAL(pix) * yap) >> 10; + pix += sow; + for(j = (1 << 14) - yap; j > Cy; j -= Cy){ + r += (R_VAL(pix) * Cy) >> 10; + g += (G_VAL(pix) * Cy) >> 10; + b += (B_VAL(pix) * Cy) >> 10; + pix += sow; + } + if(j > 0){ + r += (R_VAL(pix) * j) >> 10; + g += (G_VAL(pix) * j) >> 10; + b += (B_VAL(pix) * j) >> 10; + } + if(XAP > 0){ + pix = ypoints[dyy + y] + xpoints[x] + 1; + rr = (R_VAL(pix) * yap) >> 10; + gg = (G_VAL(pix) * yap) >> 10; + bb = (B_VAL(pix) * yap) >> 10; + pix += sow; + for(j = (1 << 14) - yap; j > Cy; j -= Cy){ + rr += (R_VAL(pix) * Cy) >> 10; + gg += (G_VAL(pix) * Cy) >> 10; + bb += (B_VAL(pix) * Cy) >> 10; + pix += sow; + } + if(j > 0){ + rr += (R_VAL(pix) * j) >> 10; + gg += (G_VAL(pix) * j) >> 10; + bb += (B_VAL(pix) * j) >> 10; + } + r = r * INV_XAP; + g = g * INV_XAP; + b = b * INV_XAP; + r = (r + ((rr * XAP))) >> 12; + g = (g + ((gg * XAP))) >> 12; + b = (b + ((bb * XAP))) >> 12; + } + else{ + r >>= 4; + g >>= 4; + b >>= 4; + } + *dptr = qRgba(r, g, b, 0xff); + dptr++; + } + } + } + /* if we're scaling down horizontally */ + else if(isi->xup_yup == 2){ + /*\ 'Correct' version, with math units prepared for MMXification \*/ + int Cx, j; + unsigned int *pix; + int r, g, b, rr, gg, bb; + int xap; + + /* go through every scanline in the output buffer */ + for(y = 0; y < dh; y++){ + dptr = dest + dx + ((y + dy) * dow); + for(x = dxx; x < end; x++){ + Cx = XAP >> 16; + xap = XAP & 0xffff; + + pix = ypoints[dyy + y] + xpoints[x]; + r = (R_VAL(pix) * xap) >> 10; + g = (G_VAL(pix) * xap) >> 10; + b = (B_VAL(pix) * xap) >> 10; + pix++; + for(j = (1 << 14) - xap; j > Cx; j -= Cx){ + r += (R_VAL(pix) * Cx) >> 10; + g += (G_VAL(pix) * Cx) >> 10; + b += (B_VAL(pix) * Cx) >> 10; + pix++; + } + if(j > 0){ + r += (R_VAL(pix) * j) >> 10; + g += (G_VAL(pix) * j) >> 10; + b += (B_VAL(pix) * j) >> 10; + } + if(YAP > 0){ + pix = ypoints[dyy + y] + xpoints[x] + sow; + rr = (R_VAL(pix) * xap) >> 10; + gg = (G_VAL(pix) * xap) >> 10; + bb = (B_VAL(pix) * xap) >> 10; + pix++; + for(j = (1 << 14) - xap; j > Cx; j -= Cx){ + rr += (R_VAL(pix) * Cx) >> 10; + gg += (G_VAL(pix) * Cx) >> 10; + bb += (B_VAL(pix) * Cx) >> 10; + pix++; + } + if(j > 0){ + rr += (R_VAL(pix) * j) >> 10; + gg += (G_VAL(pix) * j) >> 10; + bb += (B_VAL(pix) * j) >> 10; + } + r = r * INV_YAP; + g = g * INV_YAP; + b = b * INV_YAP; + r = (r + ((rr * YAP))) >> 12; + g = (g + ((gg * YAP))) >> 12; + b = (b + ((bb * YAP))) >> 12; + } + else{ + r >>= 4; + g >>= 4; + b >>= 4; + } + *dptr = qRgba(r, g, b, 0xff); + dptr++; + } + } + } + /* fully optimized (i think) - onyl change of algorithm can help */ + /* if we're scaling down horizontally & vertically */ + else{ + /*\ 'Correct' version, with math units prepared for MMXification \*/ + int Cx, Cy, i, j; + unsigned int *pix; + int r, g, b, rx, gx, bx; + int xap, yap; + + for(y = 0; y < dh; y++){ + Cy = YAP >> 16; + yap = YAP & 0xffff; + + dptr = dest + dx + ((y + dy) * dow); + for(x = dxx; x < end; x++){ + Cx = XAP >> 16; + xap = XAP & 0xffff; + + sptr = ypoints[dyy + y] + xpoints[x]; + pix = sptr; + sptr += sow; + rx = (R_VAL(pix) * xap) >> 9; + gx = (G_VAL(pix) * xap) >> 9; + bx = (B_VAL(pix) * xap) >> 9; + pix++; + for(i = (1 << 14) - xap; i > Cx; i -= Cx){ + rx += (R_VAL(pix) * Cx) >> 9; + gx += (G_VAL(pix) * Cx) >> 9; + bx += (B_VAL(pix) * Cx) >> 9; + pix++; + } + if(i > 0){ + rx += (R_VAL(pix) * i) >> 9; + gx += (G_VAL(pix) * i) >> 9; + bx += (B_VAL(pix) * i) >> 9; + } + + r = (rx * yap) >> 14; + g = (gx * yap) >> 14; + b = (bx * yap) >> 14; + + for(j = (1 << 14) - yap; j > Cy; j -= Cy){ + pix = sptr; + sptr += sow; + rx = (R_VAL(pix) * xap) >> 9; + gx = (G_VAL(pix) * xap) >> 9; + bx = (B_VAL(pix) * xap) >> 9; + pix++; + for(i = (1 << 14) - xap; i > Cx; i -= Cx){ + rx += (R_VAL(pix) * Cx) >> 9; + gx += (G_VAL(pix) * Cx) >> 9; + bx += (B_VAL(pix) * Cx) >> 9; + pix++; + } + if(i > 0){ + rx += (R_VAL(pix) * i) >> 9; + gx += (G_VAL(pix) * i) >> 9; + bx += (B_VAL(pix) * i) >> 9; + } + + r += (rx * Cy) >> 14; + g += (gx * Cy) >> 14; + b += (bx * Cy) >> 14; + } + if(j > 0){ + pix = sptr; + sptr += sow; + rx = (R_VAL(pix) * xap) >> 9; + gx = (G_VAL(pix) * xap) >> 9; + bx = (B_VAL(pix) * xap) >> 9; + pix++; + for(i = (1 << 14) - xap; i > Cx; i -= Cx){ + rx += (R_VAL(pix) * Cx) >> 9; + gx += (G_VAL(pix) * Cx) >> 9; + bx += (B_VAL(pix) * Cx) >> 9; + pix++; + } + if(i > 0){ + rx += (R_VAL(pix) * i) >> 9; + gx += (G_VAL(pix) * i) >> 9; + bx += (B_VAL(pix) * i) >> 9; + } + + r += (rx * j) >> 14; + g += (gx * j) >> 14; + b += (bx * j) >> 14; + } + + R_VAL(dptr) = r >> 5; + G_VAL(dptr) = g >> 5; + B_VAL(dptr) = b >> 5; + dptr++; + } + } + } +} + +// Imlib2/Mosfet code end + + +// public functions : +// ------------------ + +// This function returns how many pixels around the zoomed area should be +// included in the image. This is used when doing incremental painting, because +// some smoothing algorithms use surrounding pixels and not including them +// could sometimes make the edges between incremental steps visible. +int extraScalePixels( SmoothAlgorithm alg, double zoom, double blur ) +{ + double filtersupport = 0; + Filter filter = NULL; + switch( alg ) { + case SMOOTH_NONE: + filter = NULL; + filtersupport = 0.0; + break; + case SMOOTH_FAST: + filter = Box; + filtersupport = 0.5; + break; + case SMOOTH_NORMAL: + filter = Triangle; + filtersupport = 1.0; + break; + case SMOOTH_BEST: +// filter = Mitchell; + filter = Bicubic; + filtersupport = 2.0; + break; + } + if( zoom == 1.0 || filtersupport == 0.0 ) return 0; + // Imlib2/Mosfet scale - I have really no idea how many pixels it needs + if( filter == Box && blur == 1.0 ) return int( 3 / zoom + 1 ); +// This is support size for ImageMagick's scaling. + double scale=blur*QMAX(1.0/zoom,1.0); + double support=scale* filtersupport; + if (support <= 0.5) support=0.5+0.000001; + return int( support + 1 ); +} + +QImage scale(const QImage& image, int width, int height, + SmoothAlgorithm alg, QImage::ScaleMode mode, double blur ) +{ + if( image.isNull()) return image.copy(); + + QSize newSize( image.size() ); + newSize.scale( QSize( width, height ), (QSize::ScaleMode)mode ); // ### remove cast in Qt 4.0 + newSize = newSize.expandedTo( QSize( 1, 1 )); // make sure it doesn't become null + + if ( newSize == image.size() ) return image.copy(); + + width = newSize.width(); + height = newSize.height(); + Filter filter = NULL; + fastfloat filtersupport; + + switch( alg ) { + case SMOOTH_NONE: + filter = NULL; + filtersupport = 0.0; + break; + case SMOOTH_FAST: + filter = Box; + filtersupport = 0.5; + break; + case SMOOTH_NORMAL: + default: + filter = Triangle; + filtersupport = 1.0; + break; + case SMOOTH_BEST: +// filter = Mitchell; + filter = Bicubic; + filtersupport = 2.0; + break; + } + + if( filter == Box && blur == 1.0 ) + return MImageScale::smoothScale( image, width, height ); + + if( filter == Box && width > image.width() && height > image.height() && blur == 1.0 ) { + filter = NULL; // Box doesn't really smooth when enlarging + } + + if( filter == NULL ) { + return SampleImage( image, width, height ); // doesn't need 32bit + } + + return ResizeImage( image.convertDepth( 32 ), width, height, filter, filtersupport, blur ); + // unlike Qt's smoothScale() this function introduces new colors to grayscale images ... oh well +} + + +} // namespace diff --git a/src/imageutils/testjpegcontent.cpp b/src/imageutils/testjpegcontent.cpp new file mode 100644 index 0000000..b533373 --- /dev/null +++ b/src/imageutils/testjpegcontent.cpp @@ -0,0 +1,256 @@ +// vim: set tabstop=4 shiftwidth=4 noexpandtab +/* +Gwenview - A simple image viewer for KDE +Copyright 2000-2004 Aurélien Gâteau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +#include <iostream> + +// Qt +#include <qdir.h> +#include <qfile.h> +#include <qimage.h> +#include <qstring.h> + +// KDE +#include <kapplication.h> +#include <kaboutdata.h> +#include <kcmdlineargs.h> +#include <kdebug.h> +#include <kfilemetainfo.h> + +// Local +#include "imageutils/imageutils.h" +#include "imageutils/jpegcontent.h" + +using namespace std; + +const char* ORIENT6_FILE="orient6.jpg"; +const int ORIENT6_WIDTH=128; // This size is the size *after* orientation +const int ORIENT6_HEIGHT=256; // has been applied +const char* CUT_FILE="cut.jpg"; +const QString ORIENT6_COMMENT="a comment"; +const QString ORIENT1_VFLIP_FILE="test_orient1_vflip.jpg"; +const char* THUMBNAIL_FILE="test_thumbnail.jpg"; +const char* TMP_FILE="tmp.jpg"; + + +class TestEnvironment { +public: + TestEnvironment() { + bool result; + QFile in(ORIENT6_FILE); + result=in.open(IO_ReadOnly); + Q_ASSERT(result); + + QFileInfo info(in); + int size=info.size()/2; + + char* data=new char[size]; + int readSize=in.readBlock(data, size); + Q_ASSERT(size==readSize); + + QFile out(CUT_FILE); + result=out.open(IO_WriteOnly); + Q_ASSERT(result); + + int wroteSize=out.writeBlock(data, size); + Q_ASSERT(size==wroteSize); + delete []data; + } + + ~TestEnvironment() { + QDir::current().remove(CUT_FILE); + } +}; + + +typedef QMap<QString,QString> MetaInfoMap; + +MetaInfoMap getMetaInfo(const QString& path) { + KFileMetaInfo fmi(path); + QStringList list=fmi.supportedKeys(); + QStringList::ConstIterator it=list.begin(); + MetaInfoMap map; + + for ( ; it!=list.end(); ++it) { + KFileMetaInfoItem item=fmi.item(*it); + map[*it]=item.string(); + } + + return map; +} + +void compareMetaInfo(const QString& path1, const QString& path2, const QStringList& ignoredKeys) { + MetaInfoMap mim1=getMetaInfo(path1); + MetaInfoMap mim2=getMetaInfo(path2); + + Q_ASSERT(mim1.keys()==mim2.keys()); + QValueList<QString> keys=mim1.keys(); + QValueList<QString>::ConstIterator it=keys.begin(); + for ( ; it!=keys.end(); ++it) { + QString key=*it; + if (ignoredKeys.contains(key)) continue; + + if (mim1[key]!=mim2[key]) { + kdError() << "Meta info differs. Key:" << key << ", V1:" << mim1[key] << ", V2:" << mim2[key] << endl; + } + } +} + + +void testResetOrientation() { + ImageUtils::JPEGContent content; + bool result; + + // Test resetOrientation without transform + result=content.load(ORIENT6_FILE); + Q_ASSERT(result); + + content.resetOrientation(); + + result=content.save(TMP_FILE); + Q_ASSERT(result); + + result=content.load(TMP_FILE); + Q_ASSERT(result); + Q_ASSERT(content.orientation() == ImageUtils::NORMAL); + + // Test resetOrientation with transform + result=content.load(ORIENT6_FILE); + Q_ASSERT(result); + + content.resetOrientation(); + content.transform(ImageUtils::ROT_90); + + result=content.save(TMP_FILE); + Q_ASSERT(result); + + result=content.load(TMP_FILE); + Q_ASSERT(result); + Q_ASSERT(content.orientation() == ImageUtils::NORMAL); +} + + +/** + * This function tests JPEGContent::transform() by applying a ROT_90 + * transformation, saving, reloading and applying a ROT_270 to undo the ROT_90. + * Saving and reloading are necessary because lossless transformation only + * happens in JPEGContent::save() + */ +void testTransform() { + bool result; + QImage finalImage, expectedImage; + + ImageUtils::JPEGContent content; + result = content.load(ORIENT6_FILE); + Q_ASSERT(result); + + content.transform(ImageUtils::ROT_90); + result = content.save(TMP_FILE); + Q_ASSERT(result); + + result = content.load(TMP_FILE); + Q_ASSERT(result); + content.transform(ImageUtils::ROT_270); + result = content.save(TMP_FILE); + Q_ASSERT(result); + + result = finalImage.load(TMP_FILE); + Q_ASSERT(result); + + result = expectedImage.load(ORIENT6_FILE); + Q_ASSERT(result); + + Q_ASSERT(finalImage == expectedImage); +} + + +void testSetComment() { + QString comment = "test comment"; + ImageUtils::JPEGContent content; + bool result; + result = content.load(ORIENT6_FILE); + Q_ASSERT(result); + + content.setComment(comment); + Q_ASSERT(content.comment() == comment); + result = content.save(TMP_FILE); + Q_ASSERT(result); + + result = content.load(TMP_FILE); + Q_ASSERT(result); + Q_ASSERT(content.comment() == comment); +} + + +int main(int argc, char* argv[]) { + TestEnvironment testEnv; + bool result; + KAboutData aboutData("testjpegcontent", "testjpegcontent", "0"); + KCmdLineArgs::init( argc, argv, &aboutData ); + KApplication kapplication; + + // Reading info + ImageUtils::JPEGContent content; + result=content.load(ORIENT6_FILE); + Q_ASSERT(result); + Q_ASSERT(content.orientation() == 6); + Q_ASSERT(content.comment() == ORIENT6_COMMENT); + Q_ASSERT(content.size() == QSize(ORIENT6_WIDTH, ORIENT6_HEIGHT)); + + // thumbnail() + QImage thumbnail=content.thumbnail(); + result=thumbnail.save(THUMBNAIL_FILE, "JPEG"); + Q_ASSERT(result); + + testResetOrientation(); + testTransform(); + testSetComment(); + + // Test that rotating a file a lot of times does not cause findJxform() to fail + result = content.load(ORIENT6_FILE); + Q_ASSERT(result); + + // 12*4 + 1 is the same as 1, since rotating four times brings you back + for(int loop=0; loop< 12*4 + 1; ++loop) { + content.transform(ImageUtils::ROT_90); + } + result = content.save(TMP_FILE); + Q_ASSERT(result); + + result = content.load(TMP_FILE); + Q_ASSERT(result); + + Q_ASSERT(content.size() == QSize(ORIENT6_HEIGHT, ORIENT6_WIDTH)); + + + // Check the other meta info are still here + QStringList ignoredKeys; + ignoredKeys << "Orientation" << "Comment"; + compareMetaInfo(ORIENT6_FILE, ORIENT1_VFLIP_FILE, ignoredKeys); + + // Test that loading and manipulating a truncated file does not crash + result=content.load(CUT_FILE); + Q_ASSERT(result); + Q_ASSERT(content.orientation() == 6); + Q_ASSERT(content.comment() == ORIENT6_COMMENT); + content.transform(ImageUtils::VFLIP); + kdWarning() << "# Next function should output errors about incomplete image" << endl; + content.save(TMP_FILE); + kdWarning() << "#" << endl; +} diff --git a/src/imageutils/transupp.c b/src/imageutils/transupp.c new file mode 100644 index 0000000..e5ec564 --- /dev/null +++ b/src/imageutils/transupp.c @@ -0,0 +1,928 @@ +/* + * transupp.c + * + * Copyright (C) 1997, Thomas G. Lane. + * 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 "jinclude.h" +#include "jpeglib.h" +#include "transupp.h" /* My own external interface */ + + +#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. + * + * 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. + * + * 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. 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. + * Notes 2,3,4 boil down to this: generally we should use the destination's + * dimensions and ignore the source's. + */ + + +LOCAL(void) +do_flip_h (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + jvirt_barray_ptr *src_coef_arrays) +/* Horizontal flip; done in-place, so no separate dest array is required */ +{ + JDIMENSION MCU_cols, comp_width, blk_x, blk_y; + 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 = dstinfo->image_width / (dstinfo->max_h_samp_factor * DCTSIZE); + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + comp_width = MCU_cols * 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++) { + 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; + } + } + } + } + } +} + + +LOCAL(void) +do_flip_v (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + 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; + 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 = dstinfo->image_height / (dstinfo->max_v_samp_factor * DCTSIZE); + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + comp_height = MCU_rows * 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 (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 - 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, + (JDIMENSION) compptr->v_samp_factor, FALSE); + } + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + if (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]; + 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], dst_buffer[offset_y], + compptr->width_in_blocks); + } + } + } + } +} + + +LOCAL(void) +do_transpose (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + jvirt_barray_ptr *src_coef_arrays, + jvirt_barray_ptr *dst_coef_arrays) +/* Transpose source into destination */ +{ + JDIMENSION dst_blk_x, dst_blk_y; + 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; + 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, + (JDIMENSION) compptr->h_samp_factor, FALSE); + for (offset_x = 0; offset_x < compptr->h_samp_factor; offset_x++) { + src_ptr = src_buffer[offset_x][dst_blk_y + offset_y]; + dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x]; + 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, + 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; + 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 = dstinfo->image_width / (dstinfo->max_h_samp_factor * DCTSIZE); + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + comp_width = MCU_cols * compptr->h_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, + (JDIMENSION) compptr->h_samp_factor, FALSE); + for (offset_x = 0; offset_x < compptr->h_samp_factor; offset_x++) { + src_ptr = src_buffer[offset_x][dst_blk_y + offset_y]; + if (dst_blk_x < comp_width) { + /* Block is within the mirrorable area. */ + dst_ptr = dst_buffer[offset_y] + [comp_width - dst_blk_x - offset_x - 1]; + 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. */ + dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x]; + 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, + 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; + 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 = dstinfo->image_height / (dstinfo->max_v_samp_factor * DCTSIZE); + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + comp_height = MCU_rows * 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, + (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 (dst_blk_y < comp_height) { + /* Block is within the mirrorable area. */ + src_ptr = src_buffer[offset_x] + [comp_height - 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]; + 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, + 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; + 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 = dstinfo->image_width / (dstinfo->max_h_samp_factor * DCTSIZE); + MCU_rows = dstinfo->image_height / (dstinfo->max_v_samp_factor * DCTSIZE); + + 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; + 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 (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 - 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, + (JDIMENSION) compptr->v_samp_factor, FALSE); + } + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + if (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]; + /* Process the blocks that can be mirrored both ways. */ + for (dst_blk_x = 0; dst_blk_x < comp_width; dst_blk_x++) { + dst_ptr = dst_row_ptr[dst_blk_x]; + src_ptr = src_row_ptr[comp_width - 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++; + } + } + } + /* Any remaining right-edge blocks are only mirrored vertically. */ + for (; 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) { + 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. */ + dst_row_ptr = dst_buffer[offset_y]; + src_row_ptr = src_buffer[offset_y]; + /* Process the blocks that can be mirrored. */ + for (dst_blk_x = 0; dst_blk_x < comp_width; dst_blk_x++) { + dst_ptr = dst_row_ptr[dst_blk_x]; + src_ptr = src_row_ptr[comp_width - dst_blk_x - 1]; + for (i = 0; i < DCTSIZE2; i += 2) { + *dst_ptr++ = *src_ptr++; + *dst_ptr++ = - *src_ptr++; + } + } + /* Any remaining right-edge blocks are only copied. */ + for (; 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 < DCTSIZE2; i++) + *dst_ptr++ = *src_ptr++; + } + } + } + } + } +} + + +LOCAL(void) +do_transverse (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + 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; + int ci, i, j, offset_x, offset_y; + JBLOCKARRAY src_buffer, dst_buffer; + JCOEFPTR src_ptr, dst_ptr; + jpeg_component_info *compptr; + + MCU_cols = dstinfo->image_width / (dstinfo->max_h_samp_factor * DCTSIZE); + MCU_rows = dstinfo->image_height / (dstinfo->max_v_samp_factor * DCTSIZE); + + 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; + 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, + (JDIMENSION) compptr->h_samp_factor, FALSE); + for (offset_x = 0; offset_x < compptr->h_samp_factor; offset_x++) { + if (dst_blk_y < comp_height) { + src_ptr = src_buffer[offset_x] + [comp_height - dst_blk_y - offset_y - 1]; + if (dst_blk_x < comp_width) { + /* Block is within the mirrorable area. */ + dst_ptr = dst_buffer[offset_y] + [comp_width - dst_blk_x - offset_x - 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 */ + dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x]; + 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 { + src_ptr = src_buffer[offset_x][dst_blk_y + offset_y]; + if (dst_blk_x < comp_width) { + /* Bottom-edge blocks are mirrored in x only */ + dst_ptr = dst_buffer[offset_y] + [comp_width - dst_blk_x - offset_x - 1]; + 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 */ + dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x]; + for (i = 0; i < DCTSIZE; i++) + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + } + } + } + } + } + } + } +} + + +/* Request any required workspace. + * + * 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). + */ + +GLOBAL(void) +jtransform_request_workspace (j_decompress_ptr srcinfo, + jpeg_transform_info *info) +{ + jvirt_barray_ptr *coef_arrays = NULL; + jpeg_component_info *compptr; + int ci; + + 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; + } + + switch (info->transform) { + case JXFORM_NONE: + case JXFORM_FLIP_H: + /* Don't need a workspace array */ + break; + case JXFORM_FLIP_V: + case JXFORM_ROT_180: + /* Need workspace arrays having same dimensions as source image. + * Note that we allocate arrays padded out to the next iMCU boundary, + * so that transform routines need not worry about missing edge blocks. + */ + coef_arrays = (jvirt_barray_ptr *) + (*srcinfo->mem->alloc_small) ((j_common_ptr) srcinfo, JPOOL_IMAGE, + SIZEOF(jvirt_barray_ptr) * info->num_components); + for (ci = 0; ci < info->num_components; ci++) { + compptr = srcinfo->comp_info + ci; + coef_arrays[ci] = (*srcinfo->mem->request_virt_barray) + ((j_common_ptr) srcinfo, JPOOL_IMAGE, FALSE, + (JDIMENSION) jround_up((long) compptr->width_in_blocks, + (long) compptr->h_samp_factor), + (JDIMENSION) jround_up((long) compptr->height_in_blocks, + (long) compptr->v_samp_factor), + (JDIMENSION) compptr->v_samp_factor); + } + break; + case JXFORM_TRANSPOSE: + case JXFORM_TRANSVERSE: + case JXFORM_ROT_90: + case JXFORM_ROT_270: + /* Need workspace arrays having transposed dimensions. + * Note that we allocate arrays padded out to the next iMCU boundary, + * so that transform routines need not worry about missing edge blocks. + */ + coef_arrays = (jvirt_barray_ptr *) + (*srcinfo->mem->alloc_small) ((j_common_ptr) srcinfo, JPOOL_IMAGE, + SIZEOF(jvirt_barray_ptr) * info->num_components); + for (ci = 0; ci < info->num_components; ci++) { + compptr = srcinfo->comp_info + ci; + coef_arrays[ci] = (*srcinfo->mem->request_virt_barray) + ((j_common_ptr) srcinfo, JPOOL_IMAGE, FALSE, + (JDIMENSION) jround_up((long) compptr->height_in_blocks, + (long) compptr->v_samp_factor), + (JDIMENSION) jround_up((long) compptr->width_in_blocks, + (long) compptr->h_samp_factor), + (JDIMENSION) compptr->h_samp_factor); + } + break; + } + info->workspace_coef_arrays = coef_arrays; +} + + +/* 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 dtemp; + UINT16 qtemp; + + /* Transpose basic image dimensions */ + dtemp = dstinfo->image_width; + dstinfo->image_width = dstinfo->image_height; + dstinfo->image_height = dtemp; + + /* 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; + } + } + } + } +} + + +/* Trim off any partial iMCUs on the indicated destination edge */ + +LOCAL(void) +trim_right_edge (j_compress_ptr dstinfo) +{ + int ci, max_h_samp_factor; + JDIMENSION MCU_cols; + + /* We have to compute max_h_samp_factor ourselves, + * because it hasn't been set yet in the destination + * (and we don't want to use the source's value). + */ + max_h_samp_factor = 1; + for (ci = 0; ci < dstinfo->num_components; ci++) { + int h_samp_factor = dstinfo->comp_info[ci].h_samp_factor; + max_h_samp_factor = MAX(max_h_samp_factor, h_samp_factor); + } + MCU_cols = dstinfo->image_width / (max_h_samp_factor * DCTSIZE); + if (MCU_cols > 0) /* can't trim to 0 pixels */ + dstinfo->image_width = MCU_cols * (max_h_samp_factor * DCTSIZE); +} + +LOCAL(void) +trim_bottom_edge (j_compress_ptr dstinfo) +{ + int ci, max_v_samp_factor; + JDIMENSION MCU_rows; + + /* We have to compute max_v_samp_factor ourselves, + * because it hasn't been set yet in the destination + * (and we don't want to use the source's value). + */ + max_v_samp_factor = 1; + for (ci = 0; ci < dstinfo->num_components; ci++) { + int v_samp_factor = dstinfo->comp_info[ci].v_samp_factor; + max_v_samp_factor = MAX(max_v_samp_factor, v_samp_factor); + } + MCU_rows = dstinfo->image_height / (max_v_samp_factor * DCTSIZE); + if (MCU_rows > 0) /* can't trim to 0 pixels */ + dstinfo->image_height = MCU_rows * (max_v_samp_factor * DCTSIZE); +} + + +/* 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) { + /* We use jpeg_set_colorspace to make sure subsidiary settings get fixed + * properly. Among other things, the target h_samp_factor & v_samp_factor + * will get set to 1, which typically won't match the source. + * In fact we do this even if the source is already grayscale; that + * provides an easy way of coercing a grayscale JPEG with funny sampling + * factors to the customary 1,1. (Some decoders fail on other factors.) + */ + if ((dstinfo->jpeg_color_space == JCS_YCbCr && + dstinfo->num_components == 3) || + (dstinfo->jpeg_color_space == JCS_GRAYSCALE && + dstinfo->num_components == 1)) { + /* We have to preserve the source's quantization table number. */ + 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); + } + } + + /* Correct the destination's image dimensions etc if necessary */ + switch (info->transform) { + case JXFORM_NONE: + /* Nothing to do */ + break; + case JXFORM_FLIP_H: + if (info->trim) + trim_right_edge(dstinfo); + break; + case JXFORM_FLIP_V: + if (info->trim) + trim_bottom_edge(dstinfo); + break; + case JXFORM_TRANSPOSE: + transpose_critical_parameters(dstinfo); + /* transpose does NOT have to trim anything */ + break; + case JXFORM_TRANSVERSE: + transpose_critical_parameters(dstinfo); + if (info->trim) { + trim_right_edge(dstinfo); + trim_bottom_edge(dstinfo); + } + break; + case JXFORM_ROT_90: + transpose_critical_parameters(dstinfo); + if (info->trim) + trim_right_edge(dstinfo); + break; + case JXFORM_ROT_180: + if (info->trim) { + trim_right_edge(dstinfo); + trim_bottom_edge(dstinfo); + } + break; + case JXFORM_ROT_270: + transpose_critical_parameters(dstinfo); + if (info->trim) + trim_bottom_edge(dstinfo); + break; + } + + /* 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_transformation (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; + + switch (info->transform) { + case JXFORM_NONE: + break; + case JXFORM_FLIP_H: + do_flip_h(srcinfo, dstinfo, src_coef_arrays); + break; + case JXFORM_FLIP_V: + do_flip_v(srcinfo, dstinfo, src_coef_arrays, dst_coef_arrays); + break; + case JXFORM_TRANSPOSE: + do_transpose(srcinfo, dstinfo, src_coef_arrays, dst_coef_arrays); + break; + case JXFORM_TRANSVERSE: + do_transverse(srcinfo, dstinfo, src_coef_arrays, dst_coef_arrays); + break; + case JXFORM_ROT_90: + do_rot_90(srcinfo, dstinfo, src_coef_arrays, dst_coef_arrays); + break; + case JXFORM_ROT_180: + do_rot_180(srcinfo, dstinfo, src_coef_arrays, dst_coef_arrays); + break; + case JXFORM_ROT_270: + do_rot_270(srcinfo, dstinfo, src_coef_arrays, dst_coef_arrays); + break; + } +} + +#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/src/imageutils/transupp.h b/src/imageutils/transupp.h new file mode 100644 index 0000000..c4292b5 --- /dev/null +++ b/src/imageutils/transupp.h @@ -0,0 +1,141 @@ +/* + * transupp.h + * + * Copyright (C) 1997, Thomas G. Lane. + * 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. + */ + +#ifndef TRANSUPP_H +#define TRANSUPP_H + +/* 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 + +/* Short forms of external names for systems with brain-damaged linkers. */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jtransform_request_workspace jTrRequest +#define jtransform_adjust_parameters jTrAdjust +#define jtransform_execute_transformation jTrExec +#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; + +/* + * 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 "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. + */ + +typedef struct { + /* Options: set by caller */ + JXFORM_CODE transform; /* image transform operator */ + boolean trim; /* if TRUE, trim partial MCUs as needed */ + boolean force_grayscale; /* if TRUE, convert color image to grayscale */ + + /* Internal workspace: caller should not touch these */ + int num_components; /* # of components in workspace */ + jvirt_barray_ptr * workspace_coef_arrays; /* workspace for transformations */ +} jpeg_transform_info; + + +#if TRANSFORMS_SUPPORTED + +/* Request any required workspace */ +EXTERN(void) 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_transformation + JPP((j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + jvirt_barray_ptr *src_coef_arrays, + jpeg_transform_info *info)); + +#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)); + +#endif + |