summaryrefslogtreecommitdiffstats
path: root/vcl/source/filter/jpeg
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--vcl/source/filter/jpeg/Exif.cxx288
-rw-r--r--vcl/source/filter/jpeg/Exif.hxx83
-rw-r--r--vcl/source/filter/jpeg/JpegReader.cxx331
-rw-r--r--vcl/source/filter/jpeg/JpegReader.hxx74
-rw-r--r--vcl/source/filter/jpeg/JpegTransform.cxx42
-rw-r--r--vcl/source/filter/jpeg/JpegTransform.hxx40
-rw-r--r--vcl/source/filter/jpeg/JpegWriter.cxx265
-rw-r--r--vcl/source/filter/jpeg/JpegWriter.hxx57
-rw-r--r--vcl/source/filter/jpeg/jinclude.h79
-rw-r--r--vcl/source/filter/jpeg/jpeg.cxx62
-rw-r--r--vcl/source/filter/jpeg/jpeg.h67
-rw-r--r--vcl/source/filter/jpeg/jpeg.hxx39
-rw-r--r--vcl/source/filter/jpeg/jpegc.cxx520
-rw-r--r--vcl/source/filter/jpeg/jpegcomp.h18
-rw-r--r--vcl/source/filter/jpeg/transupp.c1570
-rw-r--r--vcl/source/filter/jpeg/transupp.h212
16 files changed, 3747 insertions, 0 deletions
diff --git a/vcl/source/filter/jpeg/Exif.cxx b/vcl/source/filter/jpeg/Exif.cxx
new file mode 100644
index 000000000..53b55f69a
--- /dev/null
+++ b/vcl/source/filter/jpeg/Exif.cxx
@@ -0,0 +1,288 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "Exif.hxx"
+#include <memory>
+#include <osl/endian.h>
+#include <tools/stream.hxx>
+
+Exif::Exif() :
+ maOrientation(exif::TOP_LEFT),
+ mbExifPresent(false)
+{}
+
+void Exif::setOrientation(exif::Orientation aOrientation) {
+ maOrientation = aOrientation;
+}
+
+exif::Orientation Exif::convertToOrientation(sal_Int32 value)
+{
+ switch(value) {
+ case 1: return exif::TOP_LEFT;
+ case 2: return exif::TOP_RIGHT;
+ case 3: return exif::BOTTOM_RIGHT;
+ case 4: return exif::BOTTOM_LEFT;
+ case 5: return exif::LEFT_TOP;
+ case 6: return exif::RIGHT_TOP;
+ case 7: return exif::RIGHT_BOTTOM;
+ case 8: return exif::LEFT_BOTTOM;
+ }
+ return exif::TOP_LEFT;
+}
+
+Degree10 Exif::getRotation() const
+{
+ switch(maOrientation) {
+ case exif::TOP_LEFT:
+ return 0_deg10;
+ case exif::BOTTOM_RIGHT:
+ return 1800_deg10;
+ case exif::RIGHT_TOP:
+ return 2700_deg10;
+ case exif::LEFT_BOTTOM:
+ return 900_deg10;
+ default:
+ break;
+ }
+ return 0_deg10;
+}
+
+bool Exif::read(SvStream& rStream)
+{
+ sal_Int32 nStreamPosition = rStream.Tell();
+ bool result = processJpeg(rStream, false);
+ rStream.Seek( nStreamPosition );
+
+ return result;
+}
+
+void Exif::write(SvStream& rStream)
+{
+ sal_Int32 nStreamPosition = rStream.Tell();
+ processJpeg(rStream, true);
+ rStream.Seek( nStreamPosition );
+}
+
+bool Exif::processJpeg(SvStream& rStream, bool bSetValue)
+{
+ sal_uInt16 aMagic16;
+ sal_uInt16 aLength;
+
+ sal_uInt32 aSize = rStream.TellEnd();
+ rStream.Seek(STREAM_SEEK_TO_BEGIN);
+
+ rStream.SetEndian( SvStreamEndian::BIG );
+ rStream.ReadUInt16( aMagic16 );
+
+ // Compare JPEG magic bytes
+ if( 0xFFD8 != aMagic16 )
+ {
+ return false;
+ }
+
+ sal_uInt32 aPreviousPosition = STREAM_SEEK_TO_BEGIN;
+
+ while(true)
+ {
+ sal_uInt8 aMarker = 0xD9;
+ sal_Int32 aCount;
+
+ for (aCount = 0; aCount < 7; aCount++)
+ {
+ rStream.ReadUChar( aMarker );
+ if (aMarker != 0xFF)
+ {
+ break;
+ }
+ if (aCount >= 6)
+ {
+ return false;
+ }
+ }
+
+ rStream.ReadUInt16( aLength );
+
+ if (aLength < 8 || aLength > rStream.remainingSize())
+ {
+ return false;
+ }
+
+ if (aMarker == 0xE1)
+ {
+ return processExif(rStream, aLength, bSetValue);
+ }
+ else if (aMarker == 0xD9)
+ {
+ return false;
+ }
+ else
+ {
+ sal_uInt32 aCurrentPosition = rStream.SeekRel(aLength-1);
+ if (aCurrentPosition == aPreviousPosition || aCurrentPosition > aSize)
+ {
+ return false;
+ }
+ aPreviousPosition = aCurrentPosition;
+ }
+ }
+ return false;
+}
+
+namespace {
+
+sal_uInt16 read16(sal_uInt8 const (& data)[2], bool littleEndian) {
+ if (littleEndian) {
+ return data[0] | (sal_uInt16(data[1]) << 8);
+ } else {
+ return data[1] | (sal_uInt16(data[0]) << 8);
+ }
+}
+
+void write16(sal_uInt16 value, sal_uInt8 (& data)[2], bool littleEndian) {
+ if (littleEndian) {
+ data[0] = value & 0xFF;
+ data[1] = value >> 8;
+ } else {
+ data[1] = value & 0xFF;
+ data[0] = value >> 8;
+ }
+}
+
+void write32(sal_uInt32 value, sal_uInt8 (& data)[4], bool littleEndian) {
+ if (littleEndian) {
+ data[0] = value & 0xFF;
+ data[1] = (value >> 8) & 0xFF;
+ data[2] = (value >> 16) & 0xFF;
+ data[3] = value >> 24;
+ } else {
+ data[3] = value & 0xFF;
+ data[2] = (value >> 8) & 0xFF;
+ data[1] = (value >> 16) & 0xFF;
+ data[0] = value >> 24;
+ }
+}
+
+}
+
+void Exif::processIFD(sal_uInt8* pExifData, sal_uInt16 aLength, sal_uInt16 aOffset, sal_uInt16 aNumberOfTags, bool bSetValue, bool littleEndian)
+{
+ ExifIFD* ifd = nullptr;
+
+ while (aOffset <= aLength - 12 && aNumberOfTags > 0)
+ {
+ ifd = reinterpret_cast<ExifIFD*>(&pExifData[aOffset]);
+ sal_uInt16 tag = read16(ifd->tag, littleEndian);
+
+ if (tag == ORIENTATION)
+ {
+ if(bSetValue)
+ {
+ write16(3, ifd->type, littleEndian);
+ write32(1, ifd->count, littleEndian);
+ write16(
+ maOrientation, reinterpret_cast<sal_uInt8 (&)[2]>(ifd->offset), littleEndian);
+ }
+ else
+ {
+ sal_uInt16 nIfdOffset = read16(
+ reinterpret_cast<sal_uInt8 (&)[2]>(ifd->offset), littleEndian);
+ maOrientation = convertToOrientation(nIfdOffset);
+ }
+ }
+
+ aNumberOfTags--;
+ aOffset += 12;
+ }
+}
+
+bool Exif::processExif(SvStream& rStream, sal_uInt16 aSectionLength, bool bSetValue)
+{
+ sal_uInt32 aMagic32;
+ sal_uInt16 aMagic16;
+
+ rStream.ReadUInt32( aMagic32 );
+ rStream.ReadUInt16( aMagic16 );
+
+ // Compare EXIF magic bytes
+ if( 0x45786966 != aMagic32 || 0x0000 != aMagic16)
+ {
+ return false;
+ }
+
+ sal_uInt16 aLength = aSectionLength - 6; // Length = Section - Header
+
+ std::unique_ptr<sal_uInt8[]> aExifData(new sal_uInt8[aLength]);
+ sal_uInt32 aExifDataBeginPosition = rStream.Tell();
+
+ rStream.ReadBytes(aExifData.get(), aLength);
+
+ // Exif detected
+ mbExifPresent = true;
+
+ TiffHeader* aTiffHeader = reinterpret_cast<TiffHeader*>(&aExifData[0]);
+
+ bool bIntel = aTiffHeader->byteOrder == 0x4949; //little-endian
+ bool bMotorola = aTiffHeader->byteOrder == 0x4D4D; //big-endian
+
+ if (!bIntel && !bMotorola)
+ {
+ return false;
+ }
+
+ bool bSwap = false;
+
+#ifdef OSL_BIGENDIAN
+ if (bIntel)
+ bSwap = true;
+#else
+ if (bMotorola)
+ bSwap = true;
+#endif
+
+ if (bSwap)
+ {
+ aTiffHeader->tagAlign = OSL_SWAPWORD(aTiffHeader->tagAlign);
+ aTiffHeader->offset = OSL_SWAPDWORD(aTiffHeader->offset);
+ }
+
+ if (aTiffHeader->tagAlign != 0x002A) // TIFF tag
+ {
+ return false;
+ }
+
+ sal_uInt16 aOffset = aTiffHeader->offset;
+
+ sal_uInt16 aNumberOfTags = aExifData[aOffset];
+ if (bSwap)
+ {
+ aNumberOfTags = ((aExifData[aOffset] << 8) | aExifData[aOffset+1]);
+ }
+
+ processIFD(aExifData.get(), aLength, aOffset+2, aNumberOfTags, bSetValue, bIntel);
+
+ if (bSetValue)
+ {
+ rStream.Seek(aExifDataBeginPosition);
+ rStream.WriteBytes(aExifData.get(), aLength);
+ }
+
+ return true;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/jpeg/Exif.hxx b/vcl/source/filter/jpeg/Exif.hxx
new file mode 100644
index 000000000..287b7ae43
--- /dev/null
+++ b/vcl/source/filter/jpeg/Exif.hxx
@@ -0,0 +1,83 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <tools/stream.hxx>
+#include <tools/degree.hxx>
+
+namespace exif {
+
+enum Orientation {
+ TOP_LEFT = 1,
+ TOP_RIGHT = 2,
+ BOTTOM_RIGHT = 3,
+ BOTTOM_LEFT = 4,
+ LEFT_TOP = 5,
+ RIGHT_TOP = 6,
+ RIGHT_BOTTOM = 7,
+ LEFT_BOTTOM = 8
+};
+};
+
+enum Tag {
+ ORIENTATION = 0x0112
+};
+
+class Exif final
+{
+private:
+ exif::Orientation maOrientation;
+ bool mbExifPresent;
+
+ bool processJpeg(SvStream& rStream, bool bSetValue);
+ bool processExif(SvStream& rStream, sal_uInt16 aLength, bool bSetValue);
+ void processIFD(sal_uInt8* pExifData, sal_uInt16 aLength, sal_uInt16 aOffset, sal_uInt16 aNumberOfTags, bool bSetValue, bool bLittleEndian);
+
+ struct ExifIFD {
+ sal_uInt8 tag[2];
+ sal_uInt8 type[2];
+ sal_uInt8 count[4];
+ sal_uInt8 offset[4];
+ };
+
+ struct TiffHeader {
+ sal_uInt16 byteOrder;
+ sal_uInt16 tagAlign;
+ sal_uInt32 offset;
+ };
+
+ static exif::Orientation convertToOrientation(sal_Int32 value);
+
+public:
+ Exif();
+
+ bool hasExif() const { return mbExifPresent;}
+
+ exif::Orientation getOrientation() const { return maOrientation;}
+ Degree10 getRotation() const;
+
+ void setOrientation(exif::Orientation orientation);
+
+ bool read(SvStream& rStream);
+ void write(SvStream& rStream);
+
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/jpeg/JpegReader.cxx b/vcl/source/filter/jpeg/JpegReader.cxx
new file mode 100644
index 000000000..c68ba88d7
--- /dev/null
+++ b/vcl/source/filter/jpeg/JpegReader.cxx
@@ -0,0 +1,331 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include "jpeg.h"
+#include <jpeglib.h>
+#include <jerror.h>
+
+#include "JpegReader.hxx"
+#include <vcl/graphicfilter.hxx>
+#include <vcl/outdev.hxx>
+#include <tools/fract.hxx>
+#include <tools/stream.hxx>
+#include <memory>
+
+#define BUFFER_SIZE 4096
+
+extern "C" {
+
+/*
+ * Initialize source --- called by jpeg_read_header
+ * before any data is actually read.
+ */
+static void init_source (j_decompress_ptr cinfo)
+{
+ SourceManagerStruct * source = reinterpret_cast<SourceManagerStruct *>(cinfo->src);
+
+ /* We reset the empty-input-file flag for each image,
+ * but we don't clear the input buffer.
+ * This is correct behavior for reading a series of images from one source.
+ */
+ source->start_of_file = TRUE;
+ source->no_data_available_failures = 0;
+}
+
+}
+
+static tools::Long StreamRead( SvStream* pStream, void* pBuffer, tools::Long nBufferSize )
+{
+ tools::Long nRead = 0;
+
+ if( pStream->GetError() != ERRCODE_IO_PENDING )
+ {
+ sal_uInt64 nInitialPosition = pStream->Tell();
+
+ nRead = static_cast<tools::Long>(pStream->ReadBytes(pBuffer, nBufferSize));
+
+ if( pStream->GetError() == ERRCODE_IO_PENDING )
+ {
+ // in order to search from the old position
+ // we temporarily reset the error
+ pStream->ResetError();
+ pStream->Seek( nInitialPosition );
+ pStream->SetError( ERRCODE_IO_PENDING );
+ }
+ }
+
+ return nRead;
+}
+
+extern "C" {
+
+static boolean fill_input_buffer (j_decompress_ptr cinfo)
+{
+ SourceManagerStruct * source = reinterpret_cast<SourceManagerStruct *>(cinfo->src);
+ size_t nbytes;
+
+ nbytes = StreamRead(source->stream, source->buffer, BUFFER_SIZE);
+
+ if (!nbytes)
+ {
+ source->no_data_available_failures++;
+ if (source->start_of_file) /* Treat empty input file as fatal error */
+ {
+ ERREXIT(cinfo, JERR_INPUT_EMPTY);
+ }
+ WARNMS(cinfo, JWRN_JPEG_EOF);
+ /* Insert a fake EOI marker */
+ source->buffer[0] = JOCTET(0xFF);
+ source->buffer[1] = JOCTET(JPEG_EOI);
+ nbytes = 2;
+ }
+
+ source->pub.next_input_byte = source->buffer;
+ source->pub.bytes_in_buffer = nbytes;
+ source->start_of_file = FALSE;
+
+ return TRUE;
+}
+
+static void skip_input_data (j_decompress_ptr cinfo, long numberOfBytes)
+{
+ SourceManagerStruct * source = reinterpret_cast<SourceManagerStruct *>(cinfo->src);
+
+ /* Just a dumb implementation for now. Could use fseek() except
+ * it doesn't work on pipes. Not clear that being smart is worth
+ * any trouble anyway --- large skips are infrequent.
+ */
+ if (numberOfBytes <= 0)
+ return;
+
+ while (numberOfBytes > static_cast<tools::Long>(source->pub.bytes_in_buffer))
+ {
+ numberOfBytes -= static_cast<tools::Long>(source->pub.bytes_in_buffer);
+ (void) fill_input_buffer(cinfo);
+
+ /* note we assume that fill_input_buffer will never return false,
+ * so suspension need not be handled.
+ */
+ }
+ source->pub.next_input_byte += static_cast<size_t>(numberOfBytes);
+ source->pub.bytes_in_buffer -= static_cast<size_t>(numberOfBytes);
+}
+
+static void term_source (j_decompress_ptr)
+{
+ /* no work necessary here */
+}
+
+}
+
+void jpeg_svstream_src (j_decompress_ptr cinfo, void* input)
+{
+ SourceManagerStruct * source;
+ SvStream* stream = static_cast<SvStream*>(input);
+
+ /* The source object and input buffer are made permanent so that a series
+ * of JPEG images can be read from the same file by calling jpeg_stdio_src
+ * only before the first one. (If we discarded the buffer at the end of
+ * one image, we'd likely lose the start of the next one.)
+ * This makes it unsafe to use this manager and a different source
+ * manager serially with the same JPEG object. Caveat programmer.
+ */
+
+ if (cinfo->src == nullptr)
+ { /* first time for this JPEG object? */
+ cinfo->src = static_cast<jpeg_source_mgr *>(
+ (*cinfo->mem->alloc_small) (reinterpret_cast<j_common_ptr>(cinfo), JPOOL_PERMANENT, sizeof(SourceManagerStruct)));
+ source = reinterpret_cast<SourceManagerStruct *>(cinfo->src);
+ source->buffer = static_cast<JOCTET *>(
+ (*cinfo->mem->alloc_small) (reinterpret_cast<j_common_ptr>(cinfo), JPOOL_PERMANENT, BUFFER_SIZE * sizeof(JOCTET)));
+ }
+
+ source = reinterpret_cast<SourceManagerStruct*>(cinfo->src);
+ source->pub.init_source = init_source;
+ source->pub.fill_input_buffer = fill_input_buffer;
+ source->pub.skip_input_data = skip_input_data;
+ source->pub.resync_to_restart = jpeg_resync_to_restart; /* use default method */
+ source->pub.term_source = term_source;
+ source->stream = stream;
+ source->pub.bytes_in_buffer = 0; /* forces fill_input_buffer on first read */
+ source->pub.next_input_byte = nullptr; /* until buffer loaded */
+}
+
+JPEGReader::JPEGReader( SvStream& rStream, GraphicFilterImportFlags nImportFlags ) :
+ mrStream ( rStream ),
+ mnLastPos ( rStream.Tell() ),
+ mnLastLines ( 0 ),
+ mbSetLogSize ( nImportFlags & GraphicFilterImportFlags::SetLogsizeForJpeg )
+{
+ maUpperName = "SVIJPEG";
+
+ if (!(nImportFlags & GraphicFilterImportFlags::UseExistingBitmap))
+ {
+ mpBitmap.reset(new Bitmap());
+ mpIncompleteAlpha.reset(new Bitmap());
+ }
+}
+
+JPEGReader::~JPEGReader()
+{
+}
+
+bool JPEGReader::CreateBitmap(JPEGCreateBitmapParam const & rParam)
+{
+ if (rParam.nWidth > SAL_MAX_INT32 / 8 || rParam.nHeight > SAL_MAX_INT32 / 8)
+ return false; // avoid overflows later
+
+ if (rParam.nWidth == 0 || rParam.nHeight == 0)
+ return false;
+
+ Size aSize(rParam.nWidth, rParam.nHeight);
+ bool bGray = rParam.bGray;
+
+ mpBitmap.reset(new Bitmap());
+
+ sal_uInt64 nSize = aSize.Width() * aSize.Height();
+
+ if (nSize > SAL_MAX_INT32 / (bGray?1:3))
+ return false;
+
+ if( bGray )
+ {
+ BitmapPalette aGrayPal( 256 );
+
+ for( sal_uInt16 n = 0; n < 256; n++ )
+ {
+ const sal_uInt8 cGray = static_cast<sal_uInt8>(n);
+ aGrayPal[ n ] = BitmapColor( cGray, cGray, cGray );
+ }
+
+ mpBitmap.reset(new Bitmap(aSize, vcl::PixelFormat::N8_BPP, &aGrayPal));
+ }
+ else
+ {
+ mpBitmap.reset(new Bitmap(aSize, vcl::PixelFormat::N24_BPP));
+ }
+
+ if (mbSetLogSize)
+ {
+ unsigned long nUnit = rParam.density_unit;
+
+ if (((1 == nUnit) || (2 == nUnit)) && rParam.X_density && rParam.Y_density )
+ {
+ Fraction aFractX( 1, rParam.X_density );
+ Fraction aFractY( 1, rParam.Y_density );
+ MapMode aMapMode( nUnit == 1 ? MapUnit::MapInch : MapUnit::MapCM, Point(), aFractX, aFractY );
+ Size aPrefSize = OutputDevice::LogicToLogic(aSize, aMapMode, MapMode(MapUnit::Map100thMM));
+
+ mpBitmap->SetPrefSize(aPrefSize);
+ mpBitmap->SetPrefMapMode(MapMode(MapUnit::Map100thMM));
+ }
+ }
+
+ return true;
+}
+
+Graphic JPEGReader::CreateIntermediateGraphic(tools::Long nLines)
+{
+ Graphic aGraphic;
+ const Size aSizePixel(mpBitmap->GetSizePixel());
+
+ if (!mnLastLines)
+ {
+ mpIncompleteAlpha.reset(new Bitmap(aSizePixel, vcl::PixelFormat::N1_BPP));
+ mpIncompleteAlpha->Erase(COL_WHITE);
+ }
+
+ if (nLines && (nLines < aSizePixel.Height()))
+ {
+ const tools::Long nNewLines = nLines - mnLastLines;
+
+ if (nNewLines > 0)
+ {
+ {
+ BitmapScopedWriteAccess pAccess(*mpIncompleteAlpha);
+ pAccess->SetFillColor(COL_BLACK);
+ pAccess->FillRect(tools::Rectangle(Point(0, mnLastLines), Size(pAccess->Width(), nNewLines)));
+ }
+
+ aGraphic = BitmapEx(*mpBitmap, *mpIncompleteAlpha);
+ }
+ else
+ {
+ aGraphic = BitmapEx(*mpBitmap);
+ }
+ }
+ else
+ {
+ aGraphic = BitmapEx(*mpBitmap);
+ }
+
+ mnLastLines = nLines;
+
+ return aGraphic;
+}
+
+ReadState JPEGReader::Read( Graphic& rGraphic, GraphicFilterImportFlags nImportFlags, BitmapScopedWriteAccess* ppAccess )
+{
+ ReadState eReadState;
+ bool bRet = false;
+
+ // seek back to the original position
+ mrStream.Seek( mnLastPos );
+
+ // read the (partial) image
+ tools::Long nLines;
+ ReadJPEG( this, &mrStream, &nLines, nImportFlags, ppAccess );
+
+ auto bUseExistingBitmap = static_cast<bool>(nImportFlags & GraphicFilterImportFlags::UseExistingBitmap);
+ if (bUseExistingBitmap || !mpBitmap->IsEmpty())
+ {
+ if( mrStream.GetError() == ERRCODE_IO_PENDING )
+ {
+ rGraphic = CreateIntermediateGraphic(nLines);
+ }
+ else
+ {
+ if (!bUseExistingBitmap)
+ rGraphic = BitmapEx(*mpBitmap);
+ }
+
+ bRet = true;
+ }
+ else if( mrStream.GetError() == ERRCODE_IO_PENDING )
+ {
+ bRet = true;
+ }
+
+ // Set status ( Pending has priority )
+ if (mrStream.GetError() == ERRCODE_IO_PENDING)
+ {
+ eReadState = JPEGREAD_NEED_MORE;
+ mrStream.ResetError();
+ }
+ else
+ {
+ eReadState = bRet ? JPEGREAD_OK : JPEGREAD_ERROR;
+ }
+
+ return eReadState;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/jpeg/JpegReader.hxx b/vcl/source/filter/jpeg/JpegReader.hxx
new file mode 100644
index 000000000..f9a2eb292
--- /dev/null
+++ b/vcl/source/filter/jpeg/JpegReader.hxx
@@ -0,0 +1,74 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#ifndef INCLUDED_VCL_SOURCE_FILTER_JPEG_JPEGREADER_HXX
+#define INCLUDED_VCL_SOURCE_FILTER_JPEG_JPEGREADER_HXX
+
+#include <vcl/graph.hxx>
+#include <vcl/bitmap.hxx>
+
+#include <bitmap/BitmapWriteAccess.hxx>
+#include <graphic/GraphicReader.hxx>
+
+enum class GraphicFilterImportFlags;
+
+enum ReadState
+{
+ JPEGREAD_OK,
+ JPEGREAD_ERROR,
+ JPEGREAD_NEED_MORE
+};
+
+struct JPEGCreateBitmapParam
+{
+ tools::ULong nWidth;
+ tools::ULong nHeight;
+ tools::ULong density_unit;
+ tools::ULong X_density;
+ tools::ULong Y_density;
+
+ bool bGray;
+};
+
+class JPEGReader : public GraphicReader
+{
+ SvStream& mrStream;
+ std::unique_ptr<Bitmap> mpBitmap;
+ std::unique_ptr<Bitmap> mpIncompleteAlpha;
+
+ tools::Long mnLastPos;
+ tools::Long mnLastLines;
+ bool mbSetLogSize;
+
+ Graphic CreateIntermediateGraphic(tools::Long nLines);
+
+public:
+ JPEGReader( SvStream& rStream, GraphicFilterImportFlags nImportFlags );
+ virtual ~JPEGReader() override;
+
+ ReadState Read(Graphic& rGraphic, GraphicFilterImportFlags nImportFlags, BitmapScopedWriteAccess* ppAccess);
+
+ bool CreateBitmap(JPEGCreateBitmapParam const & param);
+
+ Bitmap& GetBitmap() { return *mpBitmap; }
+};
+
+#endif // INCLUDED_VCL_SOURCE_FILTER_JPEG_JPEGREADER_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/jpeg/JpegTransform.cxx b/vcl/source/filter/jpeg/JpegTransform.cxx
new file mode 100644
index 000000000..16c0c060b
--- /dev/null
+++ b/vcl/source/filter/jpeg/JpegTransform.cxx
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include "jpeg.h"
+
+#include "JpegTransform.hxx"
+
+JpegTransform::JpegTransform(SvStream& rInputStream, SvStream& rOutputStream) :
+ maRotate ( 0 ),
+ mrInputStream ( rInputStream ),
+ mrOutputStream ( rOutputStream )
+{}
+
+void JpegTransform::perform()
+{
+ Transform( &mrInputStream, &mrOutputStream, maRotate );
+}
+
+void JpegTransform::setRotate(Degree10 aRotate)
+{
+ maRotate = aRotate;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/jpeg/JpegTransform.hxx b/vcl/source/filter/jpeg/JpegTransform.hxx
new file mode 100644
index 000000000..2b5c6dfd6
--- /dev/null
+++ b/vcl/source/filter/jpeg/JpegTransform.hxx
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#ifndef INCLUDED_VCL_SOURCE_FILTER_JPEG_JPEGTRANSFORM_HXX
+#define INCLUDED_VCL_SOURCE_FILTER_JPEG_JPEGTRANSFORM_HXX
+
+#include <tools/stream.hxx>
+
+class JpegTransform final
+{
+ Degree10 maRotate;
+ SvStream& mrInputStream;
+ SvStream& mrOutputStream;
+
+public:
+ JpegTransform(SvStream& rInputStream, SvStream& rOutputStream);
+
+ void setRotate(Degree10 aRotate);
+ void perform();
+};
+
+#endif // INCLUDED_VCL_SOURCE_FILTER_JPEG_JPEGTRANSFORM_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/jpeg/JpegWriter.cxx b/vcl/source/filter/jpeg/JpegWriter.cxx
new file mode 100644
index 000000000..026ab887b
--- /dev/null
+++ b/vcl/source/filter/jpeg/JpegWriter.cxx
@@ -0,0 +1,265 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+#include <sal/log.hxx>
+
+#include "jpeg.h"
+#include <jpeglib.h>
+#include <jerror.h>
+
+#include "JpegWriter.hxx"
+#include <vcl/BitmapReadAccess.hxx>
+#include <vcl/FilterConfigItem.hxx>
+#include <tools/helpers.hxx>
+#include <tools/stream.hxx>
+
+#define BUFFER_SIZE 4096
+
+namespace {
+
+struct DestinationManagerStruct
+{
+ jpeg_destination_mgr pub; /* public fields */
+ SvStream* stream; /* target stream */
+ JOCTET * buffer; /* start of buffer */
+};
+
+}
+
+extern "C" {
+
+static void init_destination (j_compress_ptr cinfo)
+{
+ DestinationManagerStruct * destination = reinterpret_cast<DestinationManagerStruct *>(cinfo->dest);
+
+ /* Allocate the output buffer -- it will be released when done with image */
+ destination->buffer = static_cast<JOCTET *>(
+ (*cinfo->mem->alloc_small) (reinterpret_cast<j_common_ptr>(cinfo), JPOOL_IMAGE, BUFFER_SIZE * sizeof(JOCTET)));
+
+ destination->pub.next_output_byte = destination->buffer;
+ destination->pub.free_in_buffer = BUFFER_SIZE;
+}
+
+static boolean empty_output_buffer (j_compress_ptr cinfo)
+{
+ DestinationManagerStruct * destination = reinterpret_cast<DestinationManagerStruct *>(cinfo->dest);
+
+ if (destination->stream->WriteBytes(destination->buffer, BUFFER_SIZE) != BUFFER_SIZE)
+ {
+ ERREXIT(cinfo, JERR_FILE_WRITE);
+ }
+
+ destination->pub.next_output_byte = destination->buffer;
+ destination->pub.free_in_buffer = BUFFER_SIZE;
+
+ return TRUE;
+}
+
+static void term_destination (j_compress_ptr cinfo)
+{
+ DestinationManagerStruct * destination = reinterpret_cast<DestinationManagerStruct *>(cinfo->dest);
+ size_t datacount = BUFFER_SIZE - destination->pub.free_in_buffer;
+
+ /* Write any data remaining in the buffer */
+ if (datacount > 0)
+ {
+ if (destination->stream->WriteBytes(destination->buffer, datacount) != datacount)
+ {
+ ERREXIT(cinfo, JERR_FILE_WRITE);
+ }
+ }
+}
+
+}
+
+void jpeg_svstream_dest (j_compress_ptr cinfo, void* output)
+{
+ SvStream* stream = static_cast<SvStream*>(output);
+ DestinationManagerStruct * destination;
+
+ /* The destination object is made permanent so that multiple JPEG images
+ * can be written to the same file without re-executing jpeg_svstream_dest.
+ * This makes it dangerous to use this manager and a different destination
+ * manager serially with the same JPEG object, because their private object
+ * sizes may be different. Caveat programmer.
+ */
+ if (cinfo->dest == nullptr)
+ { /* first time for this JPEG object? */
+ cinfo->dest = static_cast<jpeg_destination_mgr*>(
+ (*cinfo->mem->alloc_small) (reinterpret_cast<j_common_ptr>(cinfo), JPOOL_PERMANENT, sizeof(DestinationManagerStruct)));
+ }
+
+ destination = reinterpret_cast<DestinationManagerStruct *>(cinfo->dest);
+ destination->pub.init_destination = init_destination;
+ destination->pub.empty_output_buffer = empty_output_buffer;
+ destination->pub.term_destination = term_destination;
+ destination->stream = stream;
+}
+
+JPEGWriter::JPEGWriter( SvStream& rStream, const css::uno::Sequence< css::beans::PropertyValue >* pFilterData, bool* pExportWasGrey ) :
+ mrStream ( rStream ),
+ mpBuffer ( nullptr ),
+ mbNative ( false ),
+ mpExpWasGrey ( pExportWasGrey )
+{
+ FilterConfigItem aConfigItem( pFilterData );
+ mbGreys = aConfigItem.ReadInt32( "ColorMode", 0 ) != 0;
+ mnQuality = aConfigItem.ReadInt32( "Quality", 75 );
+ maChromaSubsampling = aConfigItem.ReadInt32( "ChromaSubsamplingMode", 0 );
+
+ if ( pFilterData )
+ {
+ for( const auto& rValue : *pFilterData )
+ {
+ if ( rValue.Name == "StatusIndicator" )
+ {
+ rValue.Value >>= mxStatusIndicator;
+ }
+ }
+ }
+}
+
+void* JPEGWriter::GetScanline( tools::Long nY )
+{
+ void* pScanline = nullptr;
+
+ if( mpReadAccess )
+ {
+ if( mbNative )
+ {
+ pScanline = mpReadAccess->GetScanline( nY );
+ }
+ else if( mpBuffer )
+ {
+ BitmapColor aColor;
+ tools::Long nWidth = mpReadAccess->Width();
+ sal_uInt8* pTmp = mpBuffer;
+
+ if( mpReadAccess->HasPalette() )
+ {
+ Scanline pScanlineRead = mpReadAccess->GetScanline( nY );
+ for( tools::Long nX = 0; nX < nWidth; nX++ )
+ {
+ aColor = mpReadAccess->GetPaletteColor( mpReadAccess->GetIndexFromData( pScanlineRead, nX ) );
+ *pTmp++ = aColor.GetRed();
+ if ( !mbGreys )
+ {
+ *pTmp++ = aColor.GetGreen();
+ *pTmp++ = aColor.GetBlue();
+ }
+ }
+ }
+ else
+ {
+ Scanline pScanlineRead = mpReadAccess->GetScanline( nY );
+ for( tools::Long nX = 0; nX < nWidth; nX++ )
+ {
+ aColor = mpReadAccess->GetPixelFromData( pScanlineRead, nX );
+ *pTmp++ = aColor.GetRed();
+ if ( !mbGreys )
+ {
+ *pTmp++ = aColor.GetGreen();
+ *pTmp++ = aColor.GetBlue();
+ }
+ }
+ }
+
+ pScanline = mpBuffer;
+ }
+ }
+
+ return pScanline;
+}
+
+bool JPEGWriter::Write( const Graphic& rGraphic )
+{
+ bool bRet = false;
+
+ if ( mxStatusIndicator.is() )
+ {
+ mxStatusIndicator->start( OUString(), 100 );
+ }
+
+ // This slightly weird logic is here to match the behaviour in ImpGraphic::ImplGetBitmap
+ // and is necessary to match pre-existing behaviour. We should probably pass down the expected
+ // background color for alpha from the higher layers.
+ Bitmap aGraphicBmp;
+ if (rGraphic.GetType() == GraphicType::Bitmap)
+ aGraphicBmp = rGraphic.GetBitmapEx().GetBitmap(COL_WHITE);
+ else
+ aGraphicBmp = rGraphic.GetBitmapEx().GetBitmap();
+
+ if ( mbGreys )
+ {
+ if ( !aGraphicBmp.Convert( BmpConversion::N8BitGreys ) )
+ aGraphicBmp = rGraphic.GetBitmapEx().GetBitmap();
+ }
+
+ mpReadAccess = Bitmap::ScopedReadAccess(aGraphicBmp);
+ if( mpReadAccess )
+ {
+ if ( !mbGreys ) // bitmap was not explicitly converted into greyscale,
+ { // check if source is greyscale only
+ bool bIsGrey = true;
+
+ tools::Long nWidth = mpReadAccess->Width();
+ for ( tools::Long nY = 0; bIsGrey && ( nY < mpReadAccess->Height() ); nY++ )
+ {
+ BitmapColor aColor;
+ Scanline pScanlineRead = mpReadAccess->GetScanline( nY );
+ for( tools::Long nX = 0; bIsGrey && ( nX < nWidth ); nX++ )
+ {
+ aColor = mpReadAccess->HasPalette() ? mpReadAccess->GetPaletteColor( mpReadAccess->GetIndexFromData( pScanlineRead, nX ) )
+ : mpReadAccess->GetPixelFromData( pScanlineRead, nX );
+ bIsGrey = ( aColor.GetRed() == aColor.GetGreen() ) && ( aColor.GetRed() == aColor.GetBlue() );
+ }
+ }
+ if ( bIsGrey )
+ mbGreys = true;
+ }
+ if( mpExpWasGrey )
+ *mpExpWasGrey = mbGreys;
+
+ if ( mbGreys )
+ mbNative = ( mpReadAccess->GetScanlineFormat() == ScanlineFormat::N8BitPal && aGraphicBmp.HasGreyPalette8Bit());
+ else
+ mbNative = ( mpReadAccess->GetScanlineFormat() == ScanlineFormat::N24BitTcRgb );
+
+ if( !mbNative )
+ mpBuffer = new sal_uInt8[ AlignedWidth4Bytes( mbGreys ? mpReadAccess->Width() * 8L : mpReadAccess->Width() * 24L ) ];
+
+ SAL_INFO("vcl", "\nJPEG Export - DPI X: " << rGraphic.GetPPI().getX() << "\nJPEG Export - DPI Y: " << rGraphic.GetPPI().getY());
+
+ bRet = WriteJPEG( this, &mrStream, mpReadAccess->Width(),
+ mpReadAccess->Height(), rGraphic.GetPPI(), mbGreys,
+ mnQuality, maChromaSubsampling, mxStatusIndicator );
+
+ delete[] mpBuffer;
+ mpBuffer = nullptr;
+
+ mpReadAccess.reset();
+ }
+ if ( mxStatusIndicator.is() )
+ mxStatusIndicator->end();
+
+ return bRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/jpeg/JpegWriter.hxx b/vcl/source/filter/jpeg/JpegWriter.hxx
new file mode 100644
index 000000000..76666cfa0
--- /dev/null
+++ b/vcl/source/filter/jpeg/JpegWriter.hxx
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#ifndef INCLUDED_VCL_SOURCE_FILTER_JPEG_JPEGWRITER_HXX
+#define INCLUDED_VCL_SOURCE_FILTER_JPEG_JPEGWRITER_HXX
+
+#include <vcl/bitmap.hxx>
+#include <vcl/BitmapReadAccess.hxx>
+#include <vcl/graph.hxx>
+
+#include <com/sun/star/uno/Sequence.h>
+#include <com/sun/star/beans/PropertyValue.hpp>
+#include <com/sun/star/task/XStatusIndicator.hpp>
+
+class JPEGWriter final
+{
+ SvStream& mrStream;
+ Bitmap::ScopedReadAccess mpReadAccess;
+ sal_uInt8* mpBuffer;
+ bool mbNative;
+ bool mbGreys;
+ sal_Int32 mnQuality;
+ sal_Int32 maChromaSubsampling;
+
+ bool* mpExpWasGrey;
+
+ css::uno::Reference< css::task::XStatusIndicator > mxStatusIndicator;
+
+public:
+ JPEGWriter( SvStream& rStream,
+ const css::uno::Sequence< css::beans::PropertyValue >* pFilterData,
+ bool* pExportWasGrey );
+
+ void* GetScanline( tools::Long nY );
+ bool Write( const Graphic& rGraphic );
+
+};
+
+#endif // INCLUDED_VCL_SOURCE_FILTER_JPEG_JPEGWRITER_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/jpeg/jinclude.h b/vcl/source/filter/jpeg/jinclude.h
new file mode 100644
index 000000000..b863b11c4
--- /dev/null
+++ b/vcl/source/filter/jpeg/jinclude.h
@@ -0,0 +1,79 @@
+/*
+ * 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))
diff --git a/vcl/source/filter/jpeg/jpeg.cxx b/vcl/source/filter/jpeg/jpeg.cxx
new file mode 100644
index 000000000..e7158b858
--- /dev/null
+++ b/vcl/source/filter/jpeg/jpeg.cxx
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "JpegReader.hxx"
+#include "JpegWriter.hxx"
+#include "jpeg.hxx"
+
+#include <vcl/graphicfilter.hxx>
+
+VCL_DLLPUBLIC bool ImportJPEG( SvStream& rInputStream, Graphic& rGraphic, GraphicFilterImportFlags nImportFlags, BitmapScopedWriteAccess* ppAccess )
+{
+ bool bReturn = true;
+
+ std::shared_ptr<GraphicReader> pContext = rGraphic.GetReaderContext();
+ rGraphic.SetReaderContext(nullptr);
+ JPEGReader* pJPEGReader = dynamic_cast<JPEGReader*>( pContext.get() );
+ if (!pJPEGReader)
+ {
+ pContext = std::make_shared<JPEGReader>( rInputStream, nImportFlags );
+ pJPEGReader = static_cast<JPEGReader*>( pContext.get() );
+ }
+
+ ReadState eReadState = pJPEGReader->Read( rGraphic, nImportFlags, ppAccess );
+
+ if( eReadState == JPEGREAD_ERROR )
+ {
+ bReturn = false;
+ }
+ else if( eReadState == JPEGREAD_NEED_MORE )
+ {
+ rGraphic.SetReaderContext( pContext );
+ }
+
+ return bReturn;
+}
+
+bool ExportJPEG(SvStream& rOutputStream, const Graphic& rGraphic,
+ const css::uno::Sequence<css::beans::PropertyValue>* pFilterData,
+ bool* pExportWasGrey)
+{
+ JPEGWriter aJPEGWriter( rOutputStream, pFilterData, pExportWasGrey );
+ bool bReturn = aJPEGWriter.Write( rGraphic );
+ return bReturn;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/jpeg/jpeg.h b/vcl/source/filter/jpeg/jpeg.h
new file mode 100644
index 000000000..a7ddcffa6
--- /dev/null
+++ b/vcl/source/filter/jpeg/jpeg.h
@@ -0,0 +1,67 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#ifndef INCLUDED_VCL_SOURCE_FILTER_JPEG_JPEG_H
+#define INCLUDED_VCL_SOURCE_FILTER_JPEG_JPEG_H
+
+#include <sal/config.h>
+
+#include <com/sun/star/uno/Reference.hxx>
+#include <basegfx/vector/b2dsize.hxx>
+#include <bitmap/BitmapWriteAccess.hxx>
+
+#include <jpeglib.h>
+
+namespace com::sun::star::task {
+ class XStatusIndicator;
+}
+class JPEGReader;
+class JPEGWriter;
+class Size;
+class SvStream;
+enum class GraphicFilterImportFlags;
+
+void jpeg_svstream_src (j_decompress_ptr cinfo, void* infile);
+
+void jpeg_svstream_dest (j_compress_ptr cinfo, void* outfile);
+
+bool WriteJPEG( JPEGWriter* pJPEGWriter, void* pOutputStream,
+ tools::Long nWidth, tools::Long nHeight, basegfx::B2DSize const & aPPI, bool bGreyScale,
+ tools::Long nQualityPercent, tools::Long aChromaSubsampling,
+ css::uno::Reference<css::task::XStatusIndicator> const & status);
+
+void ReadJPEG( JPEGReader* pJPEGReader, void* pInputStream, tools::Long* pLines,
+ GraphicFilterImportFlags nImportFlags,
+ BitmapScopedWriteAccess* ppAccess );
+
+void Transform(void* pInputStream, void* pOutputStream, Degree10 nAngle);
+
+/* Expanded data source object for stdio input */
+
+struct SourceManagerStruct {
+ jpeg_source_mgr pub; /* public fields */
+ SvStream* stream; /* source stream */
+ JOCTET* buffer; /* start of buffer */
+ boolean start_of_file; /* have we gotten any data yet? */
+ int no_data_available_failures;
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/jpeg/jpeg.hxx b/vcl/source/filter/jpeg/jpeg.hxx
new file mode 100644
index 000000000..96c9280c2
--- /dev/null
+++ b/vcl/source/filter/jpeg/jpeg.hxx
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#ifndef INCLUDED_VCL_SOURCE_FILTER_JPEG_JPEG_HXX
+#define INCLUDED_VCL_SOURCE_FILTER_JPEG_JPEG_HXX
+
+#include <vcl/graph.hxx>
+#include <vcl/graphicfilter.hxx>
+
+#include <bitmap/BitmapWriteAccess.hxx>
+
+#include <com/sun/star/uno/Sequence.h>
+
+VCL_DLLPUBLIC bool ImportJPEG( SvStream& rInputStream, Graphic& rGraphic, GraphicFilterImportFlags nImportFlags, BitmapScopedWriteAccess* ppAccess );
+
+bool ExportJPEG(SvStream& rOutputStream,
+ const Graphic& rGraphic,
+ const css::uno::Sequence< css::beans::PropertyValue >* pFilterData,
+ bool* pExportWasGrey);
+
+#endif // INCLUDED_VCL_SOURCE_FILTER_JPEG_JPEG_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/jpeg/jpegc.cxx b/vcl/source/filter/jpeg/jpegc.cxx
new file mode 100644
index 000000000..8807927a8
--- /dev/null
+++ b/vcl/source/filter/jpeg/jpegc.cxx
@@ -0,0 +1,520 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+#include <sal/log.hxx>
+#include <o3tl/float_int_conversion.hxx>
+#include <o3tl/safeint.hxx>
+
+#include <stdio.h>
+#include <setjmp.h>
+#include <jpeglib.h>
+
+#include <com/sun/star/task/XStatusIndicator.hpp>
+
+extern "C" {
+#include "transupp.h"
+}
+
+#include "jpeg.h"
+#include "JpegReader.hxx"
+#include "JpegWriter.hxx"
+#include <memory>
+#include <unotools/configmgr.hxx>
+#include <vcl/graphicfilter.hxx>
+
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning (disable: 4324) /* disable to __declspec(align()) aligned warning */
+#endif
+
+namespace {
+
+struct ErrorManagerStruct
+{
+ jpeg_error_mgr pub;
+ jmp_buf setjmp_buffer;
+};
+
+}
+
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+
+extern "C" {
+
+static void errorExit (j_common_ptr cinfo)
+{
+ char buffer[JMSG_LENGTH_MAX];
+ (*cinfo->err->format_message) (cinfo, buffer);
+ SAL_WARN("vcl.filter", "fatal failure reading JPEG: " << buffer);
+ ErrorManagerStruct * error = reinterpret_cast<ErrorManagerStruct *>(cinfo->err);
+ longjmp(error->setjmp_buffer, 1);
+}
+
+static void outputMessage (j_common_ptr cinfo)
+{
+ char buffer[JMSG_LENGTH_MAX];
+ (*cinfo->err->format_message) (cinfo, buffer);
+ SAL_WARN("vcl.filter", "failure reading JPEG: " << buffer);
+}
+
+}
+
+static int GetWarningLimit()
+{
+ return utl::ConfigManager::IsFuzzing() ? 5 : 1000;
+}
+
+extern "C" {
+
+static void emitMessage (j_common_ptr cinfo, int msg_level)
+{
+ if (msg_level < 0)
+ {
+ // ofz#3002 try to retain some degree of recoverability up to some
+ // reasonable limit (initially using ImageMagick's current limit of
+ // 1000), then bail.
+ // https://libjpeg-turbo.org/pmwiki/uploads/About/TwoIssueswiththeJPEGStandard.pdf
+ if (++cinfo->err->num_warnings > GetWarningLimit())
+ cinfo->err->error_exit(cinfo);
+ else
+ cinfo->err->output_message(cinfo);
+ }
+ else if (cinfo->err->trace_level >= msg_level)
+ cinfo->err->output_message(cinfo);
+}
+
+}
+
+namespace {
+
+class JpegDecompressOwner
+{
+public:
+ void set(jpeg_decompress_struct *cinfo)
+ {
+ m_cinfo = cinfo;
+ }
+ ~JpegDecompressOwner()
+ {
+ if (m_cinfo != nullptr)
+ {
+ jpeg_destroy_decompress(m_cinfo);
+ }
+ }
+private:
+ jpeg_decompress_struct *m_cinfo = nullptr;
+};
+
+class JpegCompressOwner
+{
+public:
+ void set(jpeg_compress_struct *cinfo)
+ {
+ m_cinfo = cinfo;
+ }
+ ~JpegCompressOwner()
+ {
+ if (m_cinfo != nullptr)
+ {
+ jpeg_destroy_compress(m_cinfo);
+ }
+ }
+private:
+ jpeg_compress_struct *m_cinfo = nullptr;
+};
+
+struct JpegStuff
+{
+ jpeg_decompress_struct cinfo;
+ ErrorManagerStruct jerr;
+ JpegDecompressOwner aOwner;
+ std::unique_ptr<BitmapScopedWriteAccess> pScopedAccess;
+ std::vector<sal_uInt8> pScanLineBuffer;
+ std::vector<sal_uInt8> pCYMKBuffer;
+};
+
+}
+
+static void ReadJPEG(JpegStuff& rContext, JPEGReader* pJPEGReader, void* pInputStream, tools::Long* pLines,
+ GraphicFilterImportFlags nImportFlags,
+ BitmapScopedWriteAccess* ppAccess)
+{
+ if (setjmp(rContext.jerr.setjmp_buffer))
+ {
+ return;
+ }
+
+ rContext.cinfo.err = jpeg_std_error(&rContext.jerr.pub);
+ rContext.jerr.pub.error_exit = errorExit;
+ rContext.jerr.pub.output_message = outputMessage;
+ rContext.jerr.pub.emit_message = emitMessage;
+
+ jpeg_create_decompress(&rContext.cinfo);
+ rContext.aOwner.set(&rContext.cinfo);
+ jpeg_svstream_src(&rContext.cinfo, pInputStream);
+ SourceManagerStruct *source = reinterpret_cast<SourceManagerStruct*>(rContext.cinfo.src);
+ jpeg_read_header(&rContext.cinfo, TRUE);
+
+ rContext.cinfo.scale_num = 1;
+ rContext.cinfo.scale_denom = 1;
+ rContext.cinfo.output_gamma = 1.0;
+ rContext.cinfo.raw_data_out = FALSE;
+ rContext.cinfo.quantize_colors = FALSE;
+
+ jpeg_calc_output_dimensions(&rContext.cinfo);
+
+ tools::Long nWidth = rContext.cinfo.output_width;
+ tools::Long nHeight = rContext.cinfo.output_height;
+
+ tools::Long nResult = 0;
+ if (utl::ConfigManager::IsFuzzing() && (o3tl::checked_multiply(nWidth, nHeight, nResult) || nResult > 4000000))
+ return;
+
+ bool bGray = (rContext.cinfo.output_components == 1);
+
+ JPEGCreateBitmapParam aCreateBitmapParam;
+
+ aCreateBitmapParam.nWidth = nWidth;
+ aCreateBitmapParam.nHeight = nHeight;
+
+ aCreateBitmapParam.density_unit = rContext.cinfo.density_unit;
+ aCreateBitmapParam.X_density = rContext.cinfo.X_density;
+ aCreateBitmapParam.Y_density = rContext.cinfo.Y_density;
+ aCreateBitmapParam.bGray = bGray;
+
+ const auto bOnlyCreateBitmap = static_cast<bool>(nImportFlags & GraphicFilterImportFlags::OnlyCreateBitmap);
+ const auto bUseExistingBitmap = static_cast<bool>(nImportFlags & GraphicFilterImportFlags::UseExistingBitmap);
+ bool bBitmapCreated = bUseExistingBitmap;
+ if (!bBitmapCreated)
+ bBitmapCreated = pJPEGReader->CreateBitmap(aCreateBitmapParam);
+
+ if (bBitmapCreated && !bOnlyCreateBitmap)
+ {
+ if (nImportFlags & GraphicFilterImportFlags::UseExistingBitmap)
+ // ppAccess must be set if this flag is used.
+ assert(ppAccess);
+ else
+ rContext.pScopedAccess.reset(new BitmapScopedWriteAccess(pJPEGReader->GetBitmap()));
+
+ BitmapScopedWriteAccess& pAccess = bUseExistingBitmap ? *ppAccess : *rContext.pScopedAccess;
+
+ if (pAccess)
+ {
+ int nPixelSize = 3;
+ J_COLOR_SPACE best_out_color_space = JCS_RGB;
+ ScanlineFormat eScanlineFormat = ScanlineFormat::N24BitTcRgb;
+ ScanlineFormat eFinalFormat = pAccess->GetScanlineFormat();
+
+ if (bGray)
+ {
+ best_out_color_space = JCS_GRAYSCALE;
+ eScanlineFormat = ScanlineFormat::N8BitPal;
+ nPixelSize = 1;
+ }
+#if defined(JCS_EXTENSIONS)
+ else if (eFinalFormat == ScanlineFormat::N24BitTcBgr)
+ {
+ best_out_color_space = JCS_EXT_BGR;
+ eScanlineFormat = eFinalFormat;
+ nPixelSize = 3;
+ }
+ else if (eFinalFormat == ScanlineFormat::N32BitTcBgra)
+ {
+ best_out_color_space = JCS_EXT_BGRA;
+ eScanlineFormat = eFinalFormat;
+ nPixelSize = 4;
+ }
+ else if (eFinalFormat == ScanlineFormat::N32BitTcRgba)
+ {
+ best_out_color_space = JCS_EXT_RGBA;
+ eScanlineFormat = eFinalFormat;
+ nPixelSize = 4;
+ }
+ else if (eFinalFormat == ScanlineFormat::N32BitTcArgb)
+ {
+ best_out_color_space = JCS_EXT_ARGB;
+ eScanlineFormat = eFinalFormat;
+ nPixelSize = 4;
+ }
+#endif
+ if (rContext.cinfo.jpeg_color_space == JCS_YCCK)
+ rContext.cinfo.out_color_space = JCS_CMYK;
+
+ if (rContext.cinfo.out_color_space != JCS_CMYK)
+ rContext.cinfo.out_color_space = best_out_color_space;
+
+ jpeg_start_decompress(&rContext.cinfo);
+
+ JSAMPLE* aRangeLimit = rContext.cinfo.sample_range_limit;
+
+ rContext.pScanLineBuffer.resize(nWidth * nPixelSize);
+
+ if (rContext.cinfo.out_color_space == JCS_CMYK)
+ {
+ rContext.pCYMKBuffer.resize(nWidth * 4);
+ }
+
+ // tdf#138950 allow up to one short read (no_data_available_failures <= 1) to not trigger cancelling import
+ for (*pLines = 0; *pLines < nHeight && source->no_data_available_failures <= 1; (*pLines)++)
+ {
+ size_t yIndex = *pLines;
+
+ sal_uInt8* p = (rContext.cinfo.out_color_space == JCS_CMYK) ? rContext.pCYMKBuffer.data() : rContext.pScanLineBuffer.data();
+ jpeg_read_scanlines(&rContext.cinfo, reinterpret_cast<JSAMPARRAY>(&p), 1);
+
+ if (rContext.cinfo.out_color_space == JCS_CMYK)
+ {
+ // convert CMYK to RGB
+ Scanline pScanline = pAccess->GetScanline(yIndex);
+ for (tools::Long cmyk = 0, x = 0; cmyk < nWidth * 4; cmyk += 4, ++x)
+ {
+ int color_C = 255 - rContext.pCYMKBuffer[cmyk + 0];
+ int color_M = 255 - rContext.pCYMKBuffer[cmyk + 1];
+ int color_Y = 255 - rContext.pCYMKBuffer[cmyk + 2];
+ int color_K = 255 - rContext.pCYMKBuffer[cmyk + 3];
+
+ sal_uInt8 cRed = aRangeLimit[255L - (color_C + color_K)];
+ sal_uInt8 cGreen = aRangeLimit[255L - (color_M + color_K)];
+ sal_uInt8 cBlue = aRangeLimit[255L - (color_Y + color_K)];
+
+ pAccess->SetPixelOnData(pScanline, x, BitmapColor(cRed, cGreen, cBlue));
+ }
+ }
+ else
+ {
+ pAccess->CopyScanline(yIndex, rContext.pScanLineBuffer.data(), eScanlineFormat, rContext.pScanLineBuffer.size());
+ }
+
+ /* PENDING ??? */
+ if (rContext.cinfo.err->msg_code == 113)
+ break;
+ }
+
+ rContext.pScanLineBuffer.clear();
+ rContext.pCYMKBuffer.clear();
+ }
+ rContext.pScopedAccess.reset();
+ }
+
+ if (bBitmapCreated && !bOnlyCreateBitmap)
+ {
+ jpeg_finish_decompress(&rContext.cinfo);
+ }
+ else
+ {
+ jpeg_abort_decompress(&rContext.cinfo);
+ }
+}
+
+void ReadJPEG( JPEGReader* pJPEGReader, void* pInputStream, tools::Long* pLines,
+ GraphicFilterImportFlags nImportFlags,
+ BitmapScopedWriteAccess* ppAccess )
+{
+ JpegStuff aContext;
+ ReadJPEG(aContext, pJPEGReader, pInputStream, pLines, nImportFlags, ppAccess);
+}
+
+bool WriteJPEG( JPEGWriter* pJPEGWriter, void* pOutputStream,
+ tools::Long nWidth, tools::Long nHeight, basegfx::B2DSize const & rPPI, bool bGreys,
+ tools::Long nQualityPercent, tools::Long aChromaSubsampling,
+ css::uno::Reference<css::task::XStatusIndicator> const & status )
+{
+ jpeg_compress_struct cinfo;
+ ErrorManagerStruct jerr;
+ void* pScanline;
+ tools::Long nY;
+
+ JpegCompressOwner aOwner;
+
+ if ( setjmp( jerr.setjmp_buffer ) )
+ {
+ return false;
+ }
+
+ cinfo.err = jpeg_std_error( &jerr.pub );
+ jerr.pub.error_exit = errorExit;
+ jerr.pub.output_message = outputMessage;
+
+ jpeg_create_compress( &cinfo );
+ aOwner.set(&cinfo);
+ jpeg_svstream_dest( &cinfo, pOutputStream );
+
+ cinfo.image_width = static_cast<JDIMENSION>(nWidth);
+ cinfo.image_height = static_cast<JDIMENSION>(nHeight);
+ if ( bGreys )
+ {
+ cinfo.input_components = 1;
+ cinfo.in_color_space = JCS_GRAYSCALE;
+ }
+ else
+ {
+ cinfo.input_components = 3;
+ cinfo.in_color_space = JCS_RGB;
+ }
+
+ jpeg_set_defaults( &cinfo );
+ jpeg_set_quality( &cinfo, static_cast<int>(nQualityPercent), FALSE );
+
+ if (o3tl::convertsToAtMost(rPPI.getX(), 65535) && o3tl::convertsToAtMost(rPPI.getY(), 65535))
+ {
+ cinfo.density_unit = 1;
+ cinfo.X_density = rPPI.getX();
+ cinfo.Y_density = rPPI.getY();
+ }
+ else
+ {
+ SAL_WARN("vcl.filter", "ignoring too large PPI " << rPPI);
+ }
+
+ if ( ( nWidth > 128 ) || ( nHeight > 128 ) )
+ jpeg_simple_progression( &cinfo );
+
+ if (aChromaSubsampling == 1) // YUV 4:4:4
+ {
+ cinfo.comp_info[0].h_samp_factor = 1;
+ cinfo.comp_info[0].v_samp_factor = 1;
+ }
+ else if (aChromaSubsampling == 2) // YUV 4:2:2
+ {
+ cinfo.comp_info[0].h_samp_factor = 2;
+ cinfo.comp_info[0].v_samp_factor = 1;
+ }
+ else if (aChromaSubsampling == 3) // YUV 4:2:0
+ {
+ cinfo.comp_info[0].h_samp_factor = 2;
+ cinfo.comp_info[0].v_samp_factor = 2;
+ }
+
+ jpeg_start_compress( &cinfo, TRUE );
+
+ for( nY = 0; nY < nHeight; nY++ )
+ {
+ pScanline = pJPEGWriter->GetScanline( nY );
+
+ if( pScanline )
+ {
+ jpeg_write_scanlines( &cinfo, reinterpret_cast<JSAMPARRAY>(&pScanline), 1 );
+ }
+
+ if( status.is() )
+ {
+ status->setValue( nY * 100L / nHeight );
+ }
+ }
+
+ jpeg_finish_compress(&cinfo);
+
+ return true;
+}
+
+void Transform(void* pInputStream, void* pOutputStream, Degree10 nAngle)
+{
+ jpeg_transform_info aTransformOption;
+ JCOPY_OPTION aCopyOption = JCOPYOPT_ALL;
+
+ jpeg_decompress_struct aSourceInfo;
+ jpeg_compress_struct aDestinationInfo;
+ ErrorManagerStruct aSourceError;
+ ErrorManagerStruct aDestinationError;
+
+ jvirt_barray_ptr* aSourceCoefArrays = nullptr;
+ jvirt_barray_ptr* aDestinationCoefArrays = nullptr;
+
+ aTransformOption.force_grayscale = FALSE;
+ aTransformOption.trim = FALSE;
+ aTransformOption.perfect = FALSE;
+ aTransformOption.crop = FALSE;
+
+ // Angle to transform option
+ // 90 Clockwise = 270 Counterclockwise
+ switch (nAngle.get())
+ {
+ case 2700:
+ aTransformOption.transform = JXFORM_ROT_90;
+ break;
+ case 1800:
+ aTransformOption.transform = JXFORM_ROT_180;
+ break;
+ case 900:
+ aTransformOption.transform = JXFORM_ROT_270;
+ break;
+ default:
+ aTransformOption.transform = JXFORM_NONE;
+ }
+
+ // Decompression
+ aSourceInfo.err = jpeg_std_error(&aSourceError.pub);
+ aSourceInfo.err->error_exit = errorExit;
+ aSourceInfo.err->output_message = outputMessage;
+
+ // Compression
+ aDestinationInfo.err = jpeg_std_error(&aDestinationError.pub);
+ aDestinationInfo.err->error_exit = errorExit;
+ aDestinationInfo.err->output_message = outputMessage;
+
+ aDestinationInfo.optimize_coding = TRUE;
+
+ JpegDecompressOwner aDecompressOwner;
+ JpegCompressOwner aCompressOwner;
+
+ if (setjmp(aSourceError.setjmp_buffer))
+ {
+ jpeg_destroy_decompress(&aSourceInfo);
+ jpeg_destroy_compress(&aDestinationInfo);
+ return;
+ }
+ if (setjmp(aDestinationError.setjmp_buffer))
+ {
+ jpeg_destroy_decompress(&aSourceInfo);
+ jpeg_destroy_compress(&aDestinationInfo);
+ return;
+ }
+
+ jpeg_create_decompress(&aSourceInfo);
+ aDecompressOwner.set(&aSourceInfo);
+ jpeg_create_compress(&aDestinationInfo);
+ aCompressOwner.set(&aDestinationInfo);
+
+ jpeg_svstream_src (&aSourceInfo, pInputStream);
+
+ jcopy_markers_setup(&aSourceInfo, aCopyOption);
+ jpeg_read_header(&aSourceInfo, TRUE);
+ jtransform_request_workspace(&aSourceInfo, &aTransformOption);
+
+ aSourceCoefArrays = jpeg_read_coefficients(&aSourceInfo);
+ jpeg_copy_critical_parameters(&aSourceInfo, &aDestinationInfo);
+
+ aDestinationCoefArrays = jtransform_adjust_parameters(&aSourceInfo, &aDestinationInfo, aSourceCoefArrays, &aTransformOption);
+ jpeg_svstream_dest (&aDestinationInfo, pOutputStream);
+
+ // Compute optimal Huffman coding tables instead of precomputed tables
+ aDestinationInfo.optimize_coding = TRUE;
+ jpeg_write_coefficients(&aDestinationInfo, aDestinationCoefArrays);
+ jcopy_markers_execute(&aSourceInfo, &aDestinationInfo, aCopyOption);
+ jtransform_execute_transformation(&aSourceInfo, &aDestinationInfo, aSourceCoefArrays, &aTransformOption);
+
+ jpeg_finish_compress(&aDestinationInfo);
+
+ jpeg_finish_decompress(&aSourceInfo);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/jpeg/jpegcomp.h b/vcl/source/filter/jpeg/jpegcomp.h
new file mode 100644
index 000000000..9b3e36775
--- /dev/null
+++ b/vcl/source/filter/jpeg/jpegcomp.h
@@ -0,0 +1,18 @@
+/*
+ * jpegcomp.h
+ *
+ * Copyright (C) 2010, D. R. Commander
+ * For conditions of distribution and use, see the accompanying README file.
+ *
+ * JPEG compatibility macros
+ * These declarations are considered internal to the JPEG library; most
+ * applications using the library shouldn't need to include this file.
+ */
+
+#if JPEG_LIB_VERSION >= 70
+#define min_DCT_h_scaled_size_ min_DCT_h_scaled_size
+#define min_DCT_v_scaled_size_ min_DCT_v_scaled_size
+#else
+#define min_DCT_h_scaled_size_ min_DCT_scaled_size
+#define min_DCT_v_scaled_size_ min_DCT_scaled_size
+#endif
diff --git a/vcl/source/filter/jpeg/transupp.c b/vcl/source/filter/jpeg/transupp.c
new file mode 100644
index 000000000..d26cb9510
--- /dev/null
+++ b/vcl/source/filter/jpeg/transupp.c
@@ -0,0 +1,1570 @@
+/*
+ * transupp.c
+ *
+ * This file was part of the Independent JPEG Group's software:
+ * Copyright (C) 1997-2011, Thomas G. Lane, Guido Vollbeding.
+ * Modifications:
+ * Copyright (C) 2010, D. R. Commander.
+ * 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.
+ */
+
+#include <sal/config.h>
+
+#include "jinclude.h"
+#include <jerror.h>
+#include <jpeglib.h>
+#include "transupp.h" /* My own external interface */
+#include "jpegcomp.h"
+
+/* Definition of jdiv_round_up is copied here from jutils.c in jpeg-8c.tar.gz,
+ just as the rest of this file appears to be copied here from transupp.c in
+ jpeg-8c.tar.gz: */
+static long
+jdiv_round_up (long a, long b)
+/* Compute a/b rounded up to next integer, ie, ceil(a/b) */
+/* Assumes a >= 0, b > 0 */
+{
+ return (a + b - 1) / b;
+}
+
+#if JPEG_LIB_VERSION >= 70
+#define dstinfo_min_DCT_h_scaled_size dstinfo->min_DCT_h_scaled_size
+#define dstinfo_min_DCT_v_scaled_size dstinfo->min_DCT_v_scaled_size
+#else
+#define dstinfo_min_DCT_h_scaled_size DCTSIZE
+#define dstinfo_min_DCT_v_scaled_size DCTSIZE
+#endif
+
+
+#if TRANSFORMS_SUPPORTED
+
+/*
+ * Lossless image transformation routines. These routines work on DCT
+ * coefficient arrays and thus do not require any lossy decompression
+ * or recompression of the image.
+ * Thanks to Guido Vollbeding for the initial design and code of this feature,
+ * and to Ben Jackson for introducing the cropping feature.
+ *
+ * Horizontal flipping is done in-place, using a single top-to-bottom
+ * pass through the virtual source array. It will thus be much the
+ * fastest option for images larger than main memory.
+ *
+ * The other routines require a set of destination virtual arrays, so they
+ * need twice as much memory as jpegtran normally does. The destination
+ * arrays are always written in normal scan order (top to bottom) because
+ * the virtual array manager expects this. The source arrays will be scanned
+ * in the corresponding order, which means multiple passes through the source
+ * arrays for most of the transforms. That could result in much thrashing
+ * if the image is larger than main memory.
+ *
+ * If cropping or trimming is involved, the destination arrays may be smaller
+ * than the source arrays. Note it is not possible to do horizontal flip
+ * in-place when a nonzero Y crop offset is specified, since we'd have to move
+ * data from one block row to another but the virtual array manager doesn't
+ * guarantee we can touch more than one row at a time. So in that case,
+ * we have to use a separate destination array.
+ *
+ * Some notes about the operating environment of the individual transform
+ * routines:
+ * 1. Both the source and destination virtual arrays are allocated from the
+ * source JPEG object, and therefore should be manipulated by calling the
+ * source's memory manager.
+ * 2. The destination's component count should be used. It may be smaller
+ * than the source's when forcing to grayscale.
+ * 3. Likewise the destination's sampling factors should be used. When
+ * forcing to grayscale the destination's sampling factors will be all 1,
+ * and we may as well take that as the effective iMCU size.
+ * 4. When "trim" is in effect, the destination's dimensions will be the
+ * trimmed values but the source's will be untrimmed.
+ * 5. When "crop" is in effect, the destination's dimensions will be the
+ * cropped values but the source's will be uncropped. Each transform
+ * routine is responsible for picking up source data starting at the
+ * correct X and Y offset for the crop region. (The X and Y offsets
+ * passed to the transform routines are measured in iMCU blocks of the
+ * destination.)
+ * 6. All the routines assume that the source and destination buffers are
+ * padded out to a full iMCU boundary. This is true, although for the
+ * source buffer it is an undocumented property of jdcoefct.c.
+ */
+
+static void lcl_jcopy_block_row (JBLOCKROW input_row, JBLOCKROW output_row, JDIMENSION num_blocks)
+/* Copy a row of coefficient blocks from one place to another. */
+{
+#ifdef FMEMCOPY
+ FMEMCOPY(output_row, input_row, num_blocks * (DCTSIZE2 * SIZEOF(JCOEF)));
+#else
+ JCOEFPTR inptr, outptr;
+ long count;
+
+ inptr = (JCOEFPTR) input_row;
+ outptr = (JCOEFPTR) output_row;
+ for (count = (long) num_blocks * DCTSIZE2; count > 0; count--) {
+ *outptr++ = *inptr++;
+ }
+#endif
+}
+
+LOCAL(void)
+do_crop (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+ JDIMENSION x_crop_offset, JDIMENSION y_crop_offset,
+ jvirt_barray_ptr *src_coef_arrays,
+ jvirt_barray_ptr *dst_coef_arrays)
+/* Crop. This is only used when no rotate/flip is requested with the crop. */
+{
+ JDIMENSION dst_blk_y, x_crop_blocks, y_crop_blocks;
+ int ci, offset_y;
+ JBLOCKARRAY src_buffer, dst_buffer;
+ jpeg_component_info *compptr;
+
+ /* We simply have to copy the right amount of data (the destination's
+ * image size) starting at the given X and Y offsets in the source.
+ */
+ for (ci = 0; ci < dstinfo->num_components; ci++) {
+ compptr = dstinfo->comp_info + ci;
+ x_crop_blocks = x_crop_offset * compptr->h_samp_factor;
+ y_crop_blocks = y_crop_offset * compptr->v_samp_factor;
+ for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks;
+ dst_blk_y += compptr->v_samp_factor) {
+ dst_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y,
+ (JDIMENSION) compptr->v_samp_factor, TRUE);
+ src_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, src_coef_arrays[ci],
+ dst_blk_y + y_crop_blocks,
+ (JDIMENSION) compptr->v_samp_factor, FALSE);
+ for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) {
+ lcl_jcopy_block_row(src_buffer[offset_y] + x_crop_blocks,
+ dst_buffer[offset_y],
+ compptr->width_in_blocks);
+ }
+ }
+ }
+}
+
+
+LOCAL(void)
+do_flip_h_no_crop (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+ JDIMENSION x_crop_offset,
+ jvirt_barray_ptr *src_coef_arrays)
+/* Horizontal flip; done in-place, so no separate dest array is required.
+ * NB: this only works when y_crop_offset is zero.
+ */
+{
+ JDIMENSION MCU_cols, comp_width, blk_x, blk_y, x_crop_blocks;
+ int ci, k, offset_y;
+ JBLOCKARRAY buffer;
+ JCOEFPTR ptr1, ptr2;
+ JCOEF temp1, temp2;
+ jpeg_component_info *compptr;
+
+ /* Horizontal mirroring of DCT blocks is accomplished by swapping
+ * pairs of blocks in-place. Within a DCT block, we perform horizontal
+ * mirroring by changing the signs of odd-numbered columns.
+ * Partial iMCUs at the right edge are left untouched.
+ */
+ MCU_cols = srcinfo->output_width /
+ (dstinfo->max_h_samp_factor * dstinfo_min_DCT_h_scaled_size);
+
+ for (ci = 0; ci < dstinfo->num_components; ci++) {
+ compptr = dstinfo->comp_info + ci;
+ comp_width = MCU_cols * compptr->h_samp_factor;
+ x_crop_blocks = x_crop_offset * compptr->h_samp_factor;
+ for (blk_y = 0; blk_y < compptr->height_in_blocks;
+ blk_y += compptr->v_samp_factor) {
+ buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, src_coef_arrays[ci], blk_y,
+ (JDIMENSION) compptr->v_samp_factor, TRUE);
+ for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) {
+ /* Do the mirroring */
+ for (blk_x = 0; blk_x * 2 < comp_width; blk_x++) {
+ ptr1 = buffer[offset_y][blk_x];
+ ptr2 = buffer[offset_y][comp_width - blk_x - 1];
+ /* this unrolled loop doesn't need to know which row it's on... */
+ for (k = 0; k < DCTSIZE2; k += 2) {
+ temp1 = *ptr1; /* swap even column */
+ temp2 = *ptr2;
+ *ptr1++ = temp2;
+ *ptr2++ = temp1;
+ temp1 = *ptr1; /* swap odd column with sign change */
+ temp2 = *ptr2;
+ *ptr1++ = -temp2;
+ *ptr2++ = -temp1;
+ }
+ }
+ if (x_crop_blocks > 0) {
+ /* Now left-justify the portion of the data to be kept.
+ * We can't use a single lcl_jcopy_block_row() call because that routine
+ * depends on memcpy(), whose behavior is unspecified for overlapping
+ * source and destination areas. Sigh.
+ */
+ for (blk_x = 0; blk_x < compptr->width_in_blocks; blk_x++) {
+ lcl_jcopy_block_row(buffer[offset_y] + blk_x + x_crop_blocks,
+ buffer[offset_y] + blk_x,
+ (JDIMENSION) 1);
+ }
+ }
+ }
+ }
+ }
+}
+
+
+LOCAL(void)
+do_flip_h (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+ JDIMENSION x_crop_offset, JDIMENSION y_crop_offset,
+ jvirt_barray_ptr *src_coef_arrays,
+ jvirt_barray_ptr *dst_coef_arrays)
+/* Horizontal flip in general cropping case */
+{
+ JDIMENSION MCU_cols, comp_width, dst_blk_x, dst_blk_y;
+ JDIMENSION x_crop_blocks, y_crop_blocks;
+ int ci, k, offset_y;
+ JBLOCKARRAY src_buffer, dst_buffer;
+ JBLOCKROW src_row_ptr, dst_row_ptr;
+ JCOEFPTR src_ptr, dst_ptr;
+ jpeg_component_info *compptr;
+
+ /* Here we must output into a separate array because we can't touch
+ * different rows of a single virtual array simultaneously. Otherwise,
+ * this is essentially the same as the routine above.
+ */
+ MCU_cols = srcinfo->output_width /
+ (dstinfo->max_h_samp_factor * dstinfo_min_DCT_h_scaled_size);
+
+ for (ci = 0; ci < dstinfo->num_components; ci++) {
+ compptr = dstinfo->comp_info + ci;
+ comp_width = MCU_cols * compptr->h_samp_factor;
+ x_crop_blocks = x_crop_offset * compptr->h_samp_factor;
+ y_crop_blocks = y_crop_offset * compptr->v_samp_factor;
+ for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks;
+ dst_blk_y += compptr->v_samp_factor) {
+ dst_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y,
+ (JDIMENSION) compptr->v_samp_factor, TRUE);
+ src_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, src_coef_arrays[ci],
+ dst_blk_y + y_crop_blocks,
+ (JDIMENSION) compptr->v_samp_factor, FALSE);
+ for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) {
+ dst_row_ptr = dst_buffer[offset_y];
+ src_row_ptr = src_buffer[offset_y];
+ for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks; dst_blk_x++) {
+ if (x_crop_blocks + dst_blk_x < comp_width) {
+ /* Do the mirrorable blocks */
+ dst_ptr = dst_row_ptr[dst_blk_x];
+ src_ptr = src_row_ptr[comp_width - x_crop_blocks - dst_blk_x - 1];
+ /* this unrolled loop doesn't need to know which row it's on... */
+ for (k = 0; k < DCTSIZE2; k += 2) {
+ *dst_ptr++ = *src_ptr++; /* copy even column */
+ *dst_ptr++ = - *src_ptr++; /* copy odd column with sign change */
+ }
+ } else {
+ /* Copy last partial block(s) verbatim */
+ lcl_jcopy_block_row(src_row_ptr + dst_blk_x + x_crop_blocks,
+ dst_row_ptr + dst_blk_x,
+ (JDIMENSION) 1);
+ }
+ }
+ }
+ }
+ }
+}
+
+
+LOCAL(void)
+do_flip_v (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+ JDIMENSION x_crop_offset, JDIMENSION y_crop_offset,
+ jvirt_barray_ptr *src_coef_arrays,
+ jvirt_barray_ptr *dst_coef_arrays)
+/* Vertical flip */
+{
+ JDIMENSION MCU_rows, comp_height, dst_blk_x, dst_blk_y;
+ JDIMENSION x_crop_blocks, y_crop_blocks;
+ int ci, i, j, offset_y;
+ JBLOCKARRAY src_buffer, dst_buffer;
+ JBLOCKROW src_row_ptr, dst_row_ptr;
+ JCOEFPTR src_ptr, dst_ptr;
+ jpeg_component_info *compptr;
+
+ /* We output into a separate array because we can't touch different
+ * rows of the source virtual array simultaneously. Otherwise, this
+ * is a pretty straightforward analog of horizontal flip.
+ * Within a DCT block, vertical mirroring is done by changing the signs
+ * of odd-numbered rows.
+ * Partial iMCUs at the bottom edge are copied verbatim.
+ */
+ MCU_rows = srcinfo->output_height /
+ (dstinfo->max_v_samp_factor * dstinfo_min_DCT_v_scaled_size);
+
+ for (ci = 0; ci < dstinfo->num_components; ci++) {
+ compptr = dstinfo->comp_info + ci;
+ comp_height = MCU_rows * compptr->v_samp_factor;
+ x_crop_blocks = x_crop_offset * compptr->h_samp_factor;
+ y_crop_blocks = y_crop_offset * compptr->v_samp_factor;
+ for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks;
+ dst_blk_y += compptr->v_samp_factor) {
+ dst_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y,
+ (JDIMENSION) compptr->v_samp_factor, TRUE);
+ if (y_crop_blocks + dst_blk_y < comp_height) {
+ /* Row is within the mirrorable area. */
+ src_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, src_coef_arrays[ci],
+ comp_height - y_crop_blocks - dst_blk_y -
+ (JDIMENSION) compptr->v_samp_factor,
+ (JDIMENSION) compptr->v_samp_factor, FALSE);
+ } else {
+ /* Bottom-edge blocks will be copied verbatim. */
+ src_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, src_coef_arrays[ci],
+ dst_blk_y + y_crop_blocks,
+ (JDIMENSION) compptr->v_samp_factor, FALSE);
+ }
+ for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) {
+ if (y_crop_blocks + dst_blk_y < comp_height) {
+ /* Row is within the mirrorable area. */
+ dst_row_ptr = dst_buffer[offset_y];
+ src_row_ptr = src_buffer[compptr->v_samp_factor - offset_y - 1];
+ src_row_ptr += x_crop_blocks;
+ for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks;
+ dst_blk_x++) {
+ dst_ptr = dst_row_ptr[dst_blk_x];
+ src_ptr = src_row_ptr[dst_blk_x];
+ for (i = 0; i < DCTSIZE; i += 2) {
+ /* copy even row */
+ for (j = 0; j < DCTSIZE; j++)
+ *dst_ptr++ = *src_ptr++;
+ /* copy odd row with sign change */
+ for (j = 0; j < DCTSIZE; j++)
+ *dst_ptr++ = - *src_ptr++;
+ }
+ }
+ } else {
+ /* Just copy row verbatim. */
+ lcl_jcopy_block_row(src_buffer[offset_y] + x_crop_blocks,
+ dst_buffer[offset_y],
+ compptr->width_in_blocks);
+ }
+ }
+ }
+ }
+}
+
+
+LOCAL(void)
+do_transpose (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+ JDIMENSION x_crop_offset, JDIMENSION y_crop_offset,
+ jvirt_barray_ptr *src_coef_arrays,
+ jvirt_barray_ptr *dst_coef_arrays)
+/* Transpose source into destination */
+{
+ JDIMENSION dst_blk_x, dst_blk_y, x_crop_blocks, y_crop_blocks;
+ int ci, i, j, offset_x, offset_y;
+ JBLOCKARRAY src_buffer, dst_buffer;
+ JCOEFPTR src_ptr, dst_ptr;
+ jpeg_component_info *compptr;
+
+ /* Transposing pixels within a block just requires transposing the
+ * DCT coefficients.
+ * Partial iMCUs at the edges require no special treatment; we simply
+ * process all the available DCT blocks for every component.
+ */
+ for (ci = 0; ci < dstinfo->num_components; ci++) {
+ compptr = dstinfo->comp_info + ci;
+ x_crop_blocks = x_crop_offset * compptr->h_samp_factor;
+ y_crop_blocks = y_crop_offset * compptr->v_samp_factor;
+ for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks;
+ dst_blk_y += compptr->v_samp_factor) {
+ dst_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y,
+ (JDIMENSION) compptr->v_samp_factor, TRUE);
+ for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) {
+ for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks;
+ dst_blk_x += compptr->h_samp_factor) {
+ src_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, src_coef_arrays[ci],
+ dst_blk_x + x_crop_blocks,
+ (JDIMENSION) compptr->h_samp_factor, FALSE);
+ for (offset_x = 0; offset_x < compptr->h_samp_factor; offset_x++) {
+ dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x];
+ src_ptr = src_buffer[offset_x][dst_blk_y + offset_y + y_crop_blocks];
+ for (i = 0; i < DCTSIZE; i++)
+ for (j = 0; j < DCTSIZE; j++)
+ dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
+ }
+ }
+ }
+ }
+ }
+}
+
+
+LOCAL(void)
+do_rot_90 (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+ JDIMENSION x_crop_offset, JDIMENSION y_crop_offset,
+ jvirt_barray_ptr *src_coef_arrays,
+ jvirt_barray_ptr *dst_coef_arrays)
+/* 90 degree rotation is equivalent to
+ * 1. Transposing the image;
+ * 2. Horizontal mirroring.
+ * These two steps are merged into a single processing routine.
+ */
+{
+ JDIMENSION MCU_cols, comp_width, dst_blk_x, dst_blk_y;
+ JDIMENSION x_crop_blocks, y_crop_blocks;
+ int ci, i, j, offset_x, offset_y;
+ JBLOCKARRAY src_buffer, dst_buffer;
+ JCOEFPTR src_ptr, dst_ptr;
+ jpeg_component_info *compptr;
+
+ /* Because of the horizontal mirror step, we can't process partial iMCUs
+ * at the (output) right edge properly. They just get transposed and
+ * not mirrored.
+ */
+ MCU_cols = srcinfo->output_height /
+ (dstinfo->max_h_samp_factor * dstinfo_min_DCT_h_scaled_size);
+
+ for (ci = 0; ci < dstinfo->num_components; ci++) {
+ compptr = dstinfo->comp_info + ci;
+ comp_width = MCU_cols * compptr->h_samp_factor;
+ x_crop_blocks = x_crop_offset * compptr->h_samp_factor;
+ y_crop_blocks = y_crop_offset * compptr->v_samp_factor;
+ for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks;
+ dst_blk_y += compptr->v_samp_factor) {
+ dst_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y,
+ (JDIMENSION) compptr->v_samp_factor, TRUE);
+ for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) {
+ for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks;
+ dst_blk_x += compptr->h_samp_factor) {
+ if (x_crop_blocks + dst_blk_x < comp_width) {
+ /* Block is within the mirrorable area. */
+ src_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, src_coef_arrays[ci],
+ comp_width - x_crop_blocks - dst_blk_x -
+ (JDIMENSION) compptr->h_samp_factor,
+ (JDIMENSION) compptr->h_samp_factor, FALSE);
+ } else {
+ /* Edge blocks are transposed but not mirrored. */
+ src_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, src_coef_arrays[ci],
+ dst_blk_x + x_crop_blocks,
+ (JDIMENSION) compptr->h_samp_factor, FALSE);
+ }
+ for (offset_x = 0; offset_x < compptr->h_samp_factor; offset_x++) {
+ dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x];
+ if (x_crop_blocks + dst_blk_x < comp_width) {
+ /* Block is within the mirrorable area. */
+ src_ptr = src_buffer[compptr->h_samp_factor - offset_x - 1]
+ [dst_blk_y + offset_y + y_crop_blocks];
+ for (i = 0; i < DCTSIZE; i++) {
+ for (j = 0; j < DCTSIZE; j++)
+ dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
+ i++;
+ for (j = 0; j < DCTSIZE; j++)
+ dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j];
+ }
+ } else {
+ /* Edge blocks are transposed but not mirrored. */
+ src_ptr = src_buffer[offset_x]
+ [dst_blk_y + offset_y + y_crop_blocks];
+ for (i = 0; i < DCTSIZE; i++)
+ for (j = 0; j < DCTSIZE; j++)
+ dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+
+LOCAL(void)
+do_rot_270 (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+ JDIMENSION x_crop_offset, JDIMENSION y_crop_offset,
+ jvirt_barray_ptr *src_coef_arrays,
+ jvirt_barray_ptr *dst_coef_arrays)
+/* 270 degree rotation is equivalent to
+ * 1. Horizontal mirroring;
+ * 2. Transposing the image.
+ * These two steps are merged into a single processing routine.
+ */
+{
+ JDIMENSION MCU_rows, comp_height, dst_blk_x, dst_blk_y;
+ JDIMENSION x_crop_blocks, y_crop_blocks;
+ int ci, i, j, offset_x, offset_y;
+ JBLOCKARRAY src_buffer, dst_buffer;
+ JCOEFPTR src_ptr, dst_ptr;
+ jpeg_component_info *compptr;
+
+ /* Because of the horizontal mirror step, we can't process partial iMCUs
+ * at the (output) bottom edge properly. They just get transposed and
+ * not mirrored.
+ */
+ MCU_rows = srcinfo->output_width /
+ (dstinfo->max_v_samp_factor * dstinfo_min_DCT_v_scaled_size);
+
+ for (ci = 0; ci < dstinfo->num_components; ci++) {
+ compptr = dstinfo->comp_info + ci;
+ comp_height = MCU_rows * compptr->v_samp_factor;
+ x_crop_blocks = x_crop_offset * compptr->h_samp_factor;
+ y_crop_blocks = y_crop_offset * compptr->v_samp_factor;
+ for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks;
+ dst_blk_y += compptr->v_samp_factor) {
+ dst_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y,
+ (JDIMENSION) compptr->v_samp_factor, TRUE);
+ for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) {
+ for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks;
+ dst_blk_x += compptr->h_samp_factor) {
+ src_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, src_coef_arrays[ci],
+ dst_blk_x + x_crop_blocks,
+ (JDIMENSION) compptr->h_samp_factor, FALSE);
+ for (offset_x = 0; offset_x < compptr->h_samp_factor; offset_x++) {
+ dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x];
+ if (y_crop_blocks + dst_blk_y < comp_height) {
+ /* Block is within the mirrorable area. */
+ src_ptr = src_buffer[offset_x]
+ [comp_height - y_crop_blocks - dst_blk_y - offset_y - 1];
+ for (i = 0; i < DCTSIZE; i++) {
+ for (j = 0; j < DCTSIZE; j++) {
+ dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
+ j++;
+ dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j];
+ }
+ }
+ } else {
+ /* Edge blocks are transposed but not mirrored. */
+ src_ptr = src_buffer[offset_x]
+ [dst_blk_y + offset_y + y_crop_blocks];
+ for (i = 0; i < DCTSIZE; i++)
+ for (j = 0; j < DCTSIZE; j++)
+ dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+
+LOCAL(void)
+do_rot_180 (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+ JDIMENSION x_crop_offset, JDIMENSION y_crop_offset,
+ jvirt_barray_ptr *src_coef_arrays,
+ jvirt_barray_ptr *dst_coef_arrays)
+/* 180 degree rotation is equivalent to
+ * 1. Vertical mirroring;
+ * 2. Horizontal mirroring.
+ * These two steps are merged into a single processing routine.
+ */
+{
+ JDIMENSION MCU_cols, MCU_rows, comp_width, comp_height, dst_blk_x, dst_blk_y;
+ JDIMENSION x_crop_blocks, y_crop_blocks;
+ int ci, i, j, offset_y;
+ JBLOCKARRAY src_buffer, dst_buffer;
+ JBLOCKROW src_row_ptr, dst_row_ptr;
+ JCOEFPTR src_ptr, dst_ptr;
+ jpeg_component_info *compptr;
+
+ MCU_cols = srcinfo->output_width /
+ (dstinfo->max_h_samp_factor * dstinfo_min_DCT_h_scaled_size);
+ MCU_rows = srcinfo->output_height /
+ (dstinfo->max_v_samp_factor * dstinfo_min_DCT_v_scaled_size);
+
+ for (ci = 0; ci < dstinfo->num_components; ci++) {
+ compptr = dstinfo->comp_info + ci;
+ comp_width = MCU_cols * compptr->h_samp_factor;
+ comp_height = MCU_rows * compptr->v_samp_factor;
+ x_crop_blocks = x_crop_offset * compptr->h_samp_factor;
+ y_crop_blocks = y_crop_offset * compptr->v_samp_factor;
+ for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks;
+ dst_blk_y += compptr->v_samp_factor) {
+ dst_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y,
+ (JDIMENSION) compptr->v_samp_factor, TRUE);
+ if (y_crop_blocks + dst_blk_y < comp_height) {
+ /* Row is within the vertically mirrorable area. */
+ src_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, src_coef_arrays[ci],
+ comp_height - y_crop_blocks - dst_blk_y -
+ (JDIMENSION) compptr->v_samp_factor,
+ (JDIMENSION) compptr->v_samp_factor, FALSE);
+ } else {
+ /* Bottom-edge rows are only mirrored horizontally. */
+ src_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, src_coef_arrays[ci],
+ dst_blk_y + y_crop_blocks,
+ (JDIMENSION) compptr->v_samp_factor, FALSE);
+ }
+ for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) {
+ dst_row_ptr = dst_buffer[offset_y];
+ if (y_crop_blocks + dst_blk_y < comp_height) {
+ /* Row is within the mirrorable area. */
+ src_row_ptr = src_buffer[compptr->v_samp_factor - offset_y - 1];
+ for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks; dst_blk_x++) {
+ dst_ptr = dst_row_ptr[dst_blk_x];
+ if (x_crop_blocks + dst_blk_x < comp_width) {
+ /* Process the blocks that can be mirrored both ways. */
+ src_ptr = src_row_ptr[comp_width - x_crop_blocks - dst_blk_x - 1];
+ for (i = 0; i < DCTSIZE; i += 2) {
+ /* For even row, negate every odd column. */
+ for (j = 0; j < DCTSIZE; j += 2) {
+ *dst_ptr++ = *src_ptr++;
+ *dst_ptr++ = - *src_ptr++;
+ }
+ /* For odd row, negate every even column. */
+ for (j = 0; j < DCTSIZE; j += 2) {
+ *dst_ptr++ = - *src_ptr++;
+ *dst_ptr++ = *src_ptr++;
+ }
+ }
+ } else {
+ /* Any remaining right-edge blocks are only mirrored vertically. */
+ src_ptr = src_row_ptr[x_crop_blocks + dst_blk_x];
+ for (i = 0; i < DCTSIZE; i += 2) {
+ for (j = 0; j < DCTSIZE; j++)
+ *dst_ptr++ = *src_ptr++;
+ for (j = 0; j < DCTSIZE; j++)
+ *dst_ptr++ = - *src_ptr++;
+ }
+ }
+ }
+ } else {
+ /* Remaining rows are just mirrored horizontally. */
+ src_row_ptr = src_buffer[offset_y];
+ for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks; dst_blk_x++) {
+ if (x_crop_blocks + dst_blk_x < comp_width) {
+ /* Process the blocks that can be mirrored. */
+ dst_ptr = dst_row_ptr[dst_blk_x];
+ src_ptr = src_row_ptr[comp_width - x_crop_blocks - dst_blk_x - 1];
+ for (i = 0; i < DCTSIZE2; i += 2) {
+ *dst_ptr++ = *src_ptr++;
+ *dst_ptr++ = - *src_ptr++;
+ }
+ } else {
+ /* Any remaining right-edge blocks are only copied. */
+ lcl_jcopy_block_row(src_row_ptr + dst_blk_x + x_crop_blocks,
+ dst_row_ptr + dst_blk_x,
+ (JDIMENSION) 1);
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+
+LOCAL(void)
+do_transverse (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+ JDIMENSION x_crop_offset, JDIMENSION y_crop_offset,
+ jvirt_barray_ptr *src_coef_arrays,
+ jvirt_barray_ptr *dst_coef_arrays)
+/* Transverse transpose is equivalent to
+ * 1. 180 degree rotation;
+ * 2. Transposition;
+ * or
+ * 1. Horizontal mirroring;
+ * 2. Transposition;
+ * 3. Horizontal mirroring.
+ * These steps are merged into a single processing routine.
+ */
+{
+ JDIMENSION MCU_cols, MCU_rows, comp_width, comp_height, dst_blk_x, dst_blk_y;
+ JDIMENSION x_crop_blocks, y_crop_blocks;
+ int ci, i, j, offset_x, offset_y;
+ JBLOCKARRAY src_buffer, dst_buffer;
+ JCOEFPTR src_ptr, dst_ptr;
+ jpeg_component_info *compptr;
+
+ MCU_cols = srcinfo->output_height /
+ (dstinfo->max_h_samp_factor * dstinfo_min_DCT_h_scaled_size);
+ MCU_rows = srcinfo->output_width /
+ (dstinfo->max_v_samp_factor * dstinfo_min_DCT_v_scaled_size);
+
+ for (ci = 0; ci < dstinfo->num_components; ci++) {
+ compptr = dstinfo->comp_info + ci;
+ comp_width = MCU_cols * compptr->h_samp_factor;
+ comp_height = MCU_rows * compptr->v_samp_factor;
+ x_crop_blocks = x_crop_offset * compptr->h_samp_factor;
+ y_crop_blocks = y_crop_offset * compptr->v_samp_factor;
+ for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks;
+ dst_blk_y += compptr->v_samp_factor) {
+ dst_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y,
+ (JDIMENSION) compptr->v_samp_factor, TRUE);
+ for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) {
+ for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks;
+ dst_blk_x += compptr->h_samp_factor) {
+ if (x_crop_blocks + dst_blk_x < comp_width) {
+ /* Block is within the mirrorable area. */
+ src_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, src_coef_arrays[ci],
+ comp_width - x_crop_blocks - dst_blk_x -
+ (JDIMENSION) compptr->h_samp_factor,
+ (JDIMENSION) compptr->h_samp_factor, FALSE);
+ } else {
+ src_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, src_coef_arrays[ci],
+ dst_blk_x + x_crop_blocks,
+ (JDIMENSION) compptr->h_samp_factor, FALSE);
+ }
+ for (offset_x = 0; offset_x < compptr->h_samp_factor; offset_x++) {
+ dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x];
+ if (y_crop_blocks + dst_blk_y < comp_height) {
+ if (x_crop_blocks + dst_blk_x < comp_width) {
+ /* Block is within the mirrorable area. */
+ src_ptr = src_buffer[compptr->h_samp_factor - offset_x - 1]
+ [comp_height - y_crop_blocks - dst_blk_y - offset_y - 1];
+ for (i = 0; i < DCTSIZE; i++) {
+ for (j = 0; j < DCTSIZE; j++) {
+ dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
+ j++;
+ dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j];
+ }
+ i++;
+ for (j = 0; j < DCTSIZE; j++) {
+ dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j];
+ j++;
+ dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
+ }
+ }
+ } else {
+ /* Right-edge blocks are mirrored in y only */
+ src_ptr = src_buffer[offset_x]
+ [comp_height - y_crop_blocks - dst_blk_y - offset_y - 1];
+ for (i = 0; i < DCTSIZE; i++) {
+ for (j = 0; j < DCTSIZE; j++) {
+ dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
+ j++;
+ dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j];
+ }
+ }
+ }
+ } else {
+ if (x_crop_blocks + dst_blk_x < comp_width) {
+ /* Bottom-edge blocks are mirrored in x only */
+ src_ptr = src_buffer[compptr->h_samp_factor - offset_x - 1]
+ [dst_blk_y + offset_y + y_crop_blocks];
+ for (i = 0; i < DCTSIZE; i++) {
+ for (j = 0; j < DCTSIZE; j++)
+ dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
+ i++;
+ for (j = 0; j < DCTSIZE; j++)
+ dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j];
+ }
+ } else {
+ /* At lower right corner, just transpose, no mirroring */
+ src_ptr = src_buffer[offset_x]
+ [dst_blk_y + offset_y + y_crop_blocks];
+ for (i = 0; i < DCTSIZE; i++)
+ for (j = 0; j < DCTSIZE; j++)
+ dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+
+/* Trim off any partial iMCUs on the indicated destination edge */
+
+LOCAL(void)
+trim_right_edge (jpeg_transform_info *info, JDIMENSION full_width)
+{
+ JDIMENSION MCU_cols;
+
+ MCU_cols = info->output_width / info->iMCU_sample_width;
+ if (MCU_cols > 0 && info->x_crop_offset + MCU_cols ==
+ full_width / info->iMCU_sample_width)
+ info->output_width = MCU_cols * info->iMCU_sample_width;
+}
+
+LOCAL(void)
+trim_bottom_edge (jpeg_transform_info *info, JDIMENSION full_height)
+{
+ JDIMENSION MCU_rows;
+
+ MCU_rows = info->output_height / info->iMCU_sample_height;
+ if (MCU_rows > 0 && info->y_crop_offset + MCU_rows ==
+ full_height / info->iMCU_sample_height)
+ info->output_height = MCU_rows * info->iMCU_sample_height;
+}
+
+
+/* Request any required workspace.
+ *
+ * This routine figures out the size that the output image will be
+ * (which implies that all the transform parameters must be set before
+ * it is called).
+ *
+ * We allocate the workspace virtual arrays from the source decompression
+ * object, so that all the arrays (both the original data and the workspace)
+ * will be taken into account while making memory management decisions.
+ * Hence, this routine must be called after jpeg_read_header (which reads
+ * the image dimensions) and before jpeg_read_coefficients (which realizes
+ * the source's virtual arrays).
+ *
+ * This function returns FALSE right away if -perfect is given
+ * and transformation is not perfect. Otherwise returns TRUE.
+ */
+
+GLOBAL(boolean)
+jtransform_request_workspace (j_decompress_ptr srcinfo,
+ jpeg_transform_info *info)
+{
+ jvirt_barray_ptr *coef_arrays;
+ boolean need_workspace, transpose_it;
+ jpeg_component_info *compptr;
+ JDIMENSION xoffset, yoffset;
+ JDIMENSION width_in_iMCUs, height_in_iMCUs;
+ JDIMENSION width_in_blocks, height_in_blocks;
+ int ci, h_samp_factor, v_samp_factor;
+
+ /* Determine number of components in output image */
+ if (info->force_grayscale &&
+ srcinfo->jpeg_color_space == JCS_YCbCr &&
+ srcinfo->num_components == 3)
+ /* We'll only process the first component */
+ info->num_components = 1;
+ else
+ /* Process all the components */
+ info->num_components = srcinfo->num_components;
+
+ /* Compute output image dimensions and related values. */
+#if JPEG_LIB_VERSION >= 80
+ jpeg_core_output_dimensions(srcinfo);
+#else
+ srcinfo->output_width = srcinfo->image_width;
+ srcinfo->output_height = srcinfo->image_height;
+#endif
+
+ /* Return right away if -perfect is given and transformation is not perfect.
+ */
+ if (info->perfect) {
+ if (info->num_components == 1) {
+ if (!jtransform_perfect_transform(srcinfo->output_width,
+ srcinfo->output_height,
+ srcinfo->min_DCT_h_scaled_size_,
+ srcinfo->min_DCT_v_scaled_size_,
+ info->transform))
+ return FALSE;
+ } else {
+ if (!jtransform_perfect_transform(srcinfo->output_width,
+ srcinfo->output_height,
+ srcinfo->max_h_samp_factor * srcinfo->min_DCT_h_scaled_size_,
+ srcinfo->max_v_samp_factor * srcinfo->min_DCT_v_scaled_size_,
+ info->transform))
+ return FALSE;
+ }
+ }
+
+ /* If there is only one output component, force the iMCU size to be 1;
+ * else use the source iMCU size. (This allows us to do the right thing
+ * when reducing color to grayscale, and also provides a handy way of
+ * cleaning up "funny" grayscale images whose sampling factors are not 1x1.)
+ */
+ switch (info->transform) {
+ case JXFORM_TRANSPOSE:
+ case JXFORM_TRANSVERSE:
+ case JXFORM_ROT_90:
+ case JXFORM_ROT_270:
+ info->output_width = srcinfo->output_height;
+ info->output_height = srcinfo->output_width;
+ if (info->num_components == 1) {
+ info->iMCU_sample_width = srcinfo->min_DCT_v_scaled_size_;
+ info->iMCU_sample_height = srcinfo->min_DCT_h_scaled_size_;
+ } else {
+ info->iMCU_sample_width =
+ srcinfo->max_v_samp_factor * srcinfo->min_DCT_v_scaled_size_;
+ info->iMCU_sample_height =
+ srcinfo->max_h_samp_factor * srcinfo->min_DCT_h_scaled_size_;
+ }
+ break;
+ default:
+ info->output_width = srcinfo->output_width;
+ info->output_height = srcinfo->output_height;
+ if (info->num_components == 1) {
+ info->iMCU_sample_width = srcinfo->min_DCT_h_scaled_size_;
+ info->iMCU_sample_height = srcinfo->min_DCT_v_scaled_size_;
+ } else {
+ info->iMCU_sample_width =
+ srcinfo->max_h_samp_factor * srcinfo->min_DCT_h_scaled_size_;
+ info->iMCU_sample_height =
+ srcinfo->max_v_samp_factor * srcinfo->min_DCT_v_scaled_size_;
+ }
+ break;
+ }
+
+ /* If cropping has been requested, compute the crop area's position and
+ * dimensions, ensuring that its upper left corner falls at an iMCU boundary.
+ */
+ if (info->crop) {
+ /* Insert default values for unset crop parameters */
+ if (info->crop_xoffset_set == JCROP_UNSET)
+ info->crop_xoffset = 0; /* default to +0 */
+ if (info->crop_yoffset_set == JCROP_UNSET)
+ info->crop_yoffset = 0; /* default to +0 */
+ if (info->crop_xoffset >= info->output_width ||
+ info->crop_yoffset >= info->output_height)
+ ERREXIT(srcinfo, JERR_CONVERSION_NOTIMPL);
+ if (info->crop_width_set == JCROP_UNSET)
+ info->crop_width = info->output_width - info->crop_xoffset;
+ if (info->crop_height_set == JCROP_UNSET)
+ info->crop_height = info->output_height - info->crop_yoffset;
+ /* Ensure parameters are valid */
+ if (info->crop_width <= 0 || info->crop_width > info->output_width ||
+ info->crop_height <= 0 || info->crop_height > info->output_height ||
+ info->crop_xoffset > info->output_width - info->crop_width ||
+ info->crop_yoffset > info->output_height - info->crop_height)
+ ERREXIT(srcinfo, JERR_CONVERSION_NOTIMPL);
+ /* Convert negative crop offsets into regular offsets */
+ if (info->crop_xoffset_set == JCROP_NEG)
+ xoffset = info->output_width - info->crop_width - info->crop_xoffset;
+ else
+ xoffset = info->crop_xoffset;
+ if (info->crop_yoffset_set == JCROP_NEG)
+ yoffset = info->output_height - info->crop_height - info->crop_yoffset;
+ else
+ yoffset = info->crop_yoffset;
+ /* Now adjust so that upper left corner falls at an iMCU boundary */
+ if (info->crop_width_set == JCROP_FORCE)
+ info->output_width = info->crop_width;
+ else
+ info->output_width =
+ info->crop_width + (xoffset % info->iMCU_sample_width);
+ if (info->crop_height_set == JCROP_FORCE)
+ info->output_height = info->crop_height;
+ else
+ info->output_height =
+ info->crop_height + (yoffset % info->iMCU_sample_height);
+ /* Save x/y offsets measured in iMCUs */
+ info->x_crop_offset = xoffset / info->iMCU_sample_width;
+ info->y_crop_offset = yoffset / info->iMCU_sample_height;
+ } else {
+ info->x_crop_offset = 0;
+ info->y_crop_offset = 0;
+ }
+
+ /* Figure out whether we need workspace arrays,
+ * and if so whether they are transposed relative to the source.
+ */
+ need_workspace = FALSE;
+ transpose_it = FALSE;
+ switch (info->transform) {
+ case JXFORM_NONE:
+ if (info->x_crop_offset != 0 || info->y_crop_offset != 0)
+ need_workspace = TRUE;
+ /* No workspace needed if neither cropping nor transforming */
+ break;
+ case JXFORM_FLIP_H:
+ if (info->trim)
+ trim_right_edge(info, srcinfo->output_width);
+ if (info->y_crop_offset != 0 || info->slow_hflip)
+ need_workspace = TRUE;
+ /* do_flip_h_no_crop doesn't need a workspace array */
+ break;
+ case JXFORM_FLIP_V:
+ if (info->trim)
+ trim_bottom_edge(info, srcinfo->output_height);
+ /* Need workspace arrays having same dimensions as source image. */
+ need_workspace = TRUE;
+ break;
+ case JXFORM_TRANSPOSE:
+ /* transpose does NOT have to trim anything */
+ /* Need workspace arrays having transposed dimensions. */
+ need_workspace = TRUE;
+ transpose_it = TRUE;
+ break;
+ case JXFORM_TRANSVERSE:
+ if (info->trim) {
+ trim_right_edge(info, srcinfo->output_height);
+ trim_bottom_edge(info, srcinfo->output_width);
+ }
+ /* Need workspace arrays having transposed dimensions. */
+ need_workspace = TRUE;
+ transpose_it = TRUE;
+ break;
+ case JXFORM_ROT_90:
+ if (info->trim)
+ trim_right_edge(info, srcinfo->output_height);
+ /* Need workspace arrays having transposed dimensions. */
+ need_workspace = TRUE;
+ transpose_it = TRUE;
+ break;
+ case JXFORM_ROT_180:
+ if (info->trim) {
+ trim_right_edge(info, srcinfo->output_width);
+ trim_bottom_edge(info, srcinfo->output_height);
+ }
+ /* Need workspace arrays having same dimensions as source image. */
+ need_workspace = TRUE;
+ break;
+ case JXFORM_ROT_270:
+ if (info->trim)
+ trim_bottom_edge(info, srcinfo->output_width);
+ /* Need workspace arrays having transposed dimensions. */
+ need_workspace = TRUE;
+ transpose_it = TRUE;
+ break;
+ }
+
+ /* Allocate workspace if needed.
+ * Note that we allocate arrays padded out to the next iMCU boundary,
+ * so that transform routines need not worry about missing edge blocks.
+ */
+ if (need_workspace) {
+ coef_arrays = (jvirt_barray_ptr *)
+ (*srcinfo->mem->alloc_small) ((j_common_ptr) srcinfo, JPOOL_IMAGE,
+ SIZEOF(jvirt_barray_ptr) * info->num_components);
+ width_in_iMCUs = (JDIMENSION)
+ jdiv_round_up((long) info->output_width,
+ (long) info->iMCU_sample_width);
+ height_in_iMCUs = (JDIMENSION)
+ jdiv_round_up((long) info->output_height,
+ (long) info->iMCU_sample_height);
+ for (ci = 0; ci < info->num_components; ci++) {
+ compptr = srcinfo->comp_info + ci;
+ if (info->num_components == 1) {
+ /* we're going to force samp factors to 1x1 in this case */
+ h_samp_factor = v_samp_factor = 1;
+ } else if (transpose_it) {
+ h_samp_factor = compptr->v_samp_factor;
+ v_samp_factor = compptr->h_samp_factor;
+ } else {
+ h_samp_factor = compptr->h_samp_factor;
+ v_samp_factor = compptr->v_samp_factor;
+ }
+ width_in_blocks = width_in_iMCUs * h_samp_factor;
+ height_in_blocks = height_in_iMCUs * v_samp_factor;
+ coef_arrays[ci] = (*srcinfo->mem->request_virt_barray)
+ ((j_common_ptr) srcinfo, JPOOL_IMAGE, FALSE,
+ width_in_blocks, height_in_blocks, (JDIMENSION) v_samp_factor);
+ }
+ info->workspace_coef_arrays = coef_arrays;
+ } else
+ info->workspace_coef_arrays = NULL;
+
+ return TRUE;
+}
+
+
+/* Transpose destination image parameters */
+
+LOCAL(void)
+transpose_critical_parameters (j_compress_ptr dstinfo)
+{
+ int tblno, i, j, ci, itemp;
+ jpeg_component_info *compptr;
+ JQUANT_TBL *qtblptr;
+ JDIMENSION jtemp;
+ UINT16 qtemp;
+
+ /* Transpose image dimensions */
+ jtemp = dstinfo->image_width;
+ dstinfo->image_width = dstinfo->image_height;
+ dstinfo->image_height = jtemp;
+#if JPEG_LIB_VERSION >= 70
+ itemp = dstinfo->min_DCT_h_scaled_size;
+ dstinfo->min_DCT_h_scaled_size = dstinfo->min_DCT_v_scaled_size;
+ dstinfo->min_DCT_v_scaled_size = itemp;
+#endif
+
+ /* Transpose sampling factors */
+ for (ci = 0; ci < dstinfo->num_components; ci++) {
+ compptr = dstinfo->comp_info + ci;
+ itemp = compptr->h_samp_factor;
+ compptr->h_samp_factor = compptr->v_samp_factor;
+ compptr->v_samp_factor = itemp;
+ }
+
+ /* Transpose quantization tables */
+ for (tblno = 0; tblno < NUM_QUANT_TBLS; tblno++) {
+ qtblptr = dstinfo->quant_tbl_ptrs[tblno];
+ if (qtblptr != NULL) {
+ for (i = 0; i < DCTSIZE; i++) {
+ for (j = 0; j < i; j++) {
+ qtemp = qtblptr->quantval[i*DCTSIZE+j];
+ qtblptr->quantval[i*DCTSIZE+j] = qtblptr->quantval[j*DCTSIZE+i];
+ qtblptr->quantval[j*DCTSIZE+i] = qtemp;
+ }
+ }
+ }
+ }
+}
+
+
+/* Adjust Exif image parameters.
+ *
+ * We try to adjust the Tags ExifImageWidth and ExifImageHeight if possible.
+ */
+
+#if JPEG_LIB_VERSION >= 70
+LOCAL(void)
+adjust_exif_parameters (JOCTET FAR * data, unsigned int length,
+ JDIMENSION new_width, JDIMENSION new_height)
+{
+ boolean is_motorola; /* Flag for byte order */
+ unsigned int number_of_tags, tagnum;
+ unsigned int firstoffset, offset;
+ JDIMENSION new_value;
+
+ if (length < 12) return; /* Length of an IFD entry */
+
+ /* Discover byte order */
+ if (GETJOCTET(data[0]) == 0x49 && GETJOCTET(data[1]) == 0x49)
+ is_motorola = FALSE;
+ else if (GETJOCTET(data[0]) == 0x4D && GETJOCTET(data[1]) == 0x4D)
+ is_motorola = TRUE;
+ else
+ return;
+
+ /* Check Tag Mark */
+ if (is_motorola) {
+ if (GETJOCTET(data[2]) != 0) return;
+ if (GETJOCTET(data[3]) != 0x2A) return;
+ } else {
+ if (GETJOCTET(data[3]) != 0) return;
+ if (GETJOCTET(data[2]) != 0x2A) return;
+ }
+
+ /* Get first IFD offset (offset to IFD0) */
+ if (is_motorola) {
+ if (GETJOCTET(data[4]) != 0) return;
+ if (GETJOCTET(data[5]) != 0) return;
+ firstoffset = GETJOCTET(data[6]);
+ firstoffset <<= 8;
+ firstoffset += GETJOCTET(data[7]);
+ } else {
+ if (GETJOCTET(data[7]) != 0) return;
+ if (GETJOCTET(data[6]) != 0) return;
+ firstoffset = GETJOCTET(data[5]);
+ firstoffset <<= 8;
+ firstoffset += GETJOCTET(data[4]);
+ }
+ if (firstoffset > length - 2) return; /* check end of data segment */
+
+ /* Get the number of directory entries contained in this IFD */
+ if (is_motorola) {
+ number_of_tags = GETJOCTET(data[firstoffset]);
+ number_of_tags <<= 8;
+ number_of_tags += GETJOCTET(data[firstoffset+1]);
+ } else {
+ number_of_tags = GETJOCTET(data[firstoffset+1]);
+ number_of_tags <<= 8;
+ number_of_tags += GETJOCTET(data[firstoffset]);
+ }
+ if (number_of_tags == 0) return;
+ firstoffset += 2;
+
+ /* Search for ExifSubIFD offset Tag in IFD0 */
+ for (;;) {
+ if (firstoffset > length - 12) return; /* check end of data segment */
+ /* Get Tag number */
+ if (is_motorola) {
+ tagnum = GETJOCTET(data[firstoffset]);
+ tagnum <<= 8;
+ tagnum += GETJOCTET(data[firstoffset+1]);
+ } else {
+ tagnum = GETJOCTET(data[firstoffset+1]);
+ tagnum <<= 8;
+ tagnum += GETJOCTET(data[firstoffset]);
+ }
+ if (tagnum == 0x8769) break; /* found ExifSubIFD offset Tag */
+ if (--number_of_tags == 0) return;
+ firstoffset += 12;
+ }
+
+ /* Get the ExifSubIFD offset */
+ if (is_motorola) {
+ if (GETJOCTET(data[firstoffset+8]) != 0) return;
+ if (GETJOCTET(data[firstoffset+9]) != 0) return;
+ offset = GETJOCTET(data[firstoffset+10]);
+ offset <<= 8;
+ offset += GETJOCTET(data[firstoffset+11]);
+ } else {
+ if (GETJOCTET(data[firstoffset+11]) != 0) return;
+ if (GETJOCTET(data[firstoffset+10]) != 0) return;
+ offset = GETJOCTET(data[firstoffset+9]);
+ offset <<= 8;
+ offset += GETJOCTET(data[firstoffset+8]);
+ }
+ if (offset > length - 2) return; /* check end of data segment */
+
+ /* Get the number of directory entries contained in this SubIFD */
+ if (is_motorola) {
+ number_of_tags = GETJOCTET(data[offset]);
+ number_of_tags <<= 8;
+ number_of_tags += GETJOCTET(data[offset+1]);
+ } else {
+ number_of_tags = GETJOCTET(data[offset+1]);
+ number_of_tags <<= 8;
+ number_of_tags += GETJOCTET(data[offset]);
+ }
+ if (number_of_tags < 2) return;
+ offset += 2;
+
+ /* Search for ExifImageWidth and ExifImageHeight Tags in this SubIFD */
+ do {
+ if (offset > length - 12) return; /* check end of data segment */
+ /* Get Tag number */
+ if (is_motorola) {
+ tagnum = GETJOCTET(data[offset]);
+ tagnum <<= 8;
+ tagnum += GETJOCTET(data[offset+1]);
+ } else {
+ tagnum = GETJOCTET(data[offset+1]);
+ tagnum <<= 8;
+ tagnum += GETJOCTET(data[offset]);
+ }
+ if (tagnum == 0xA002 || tagnum == 0xA003) {
+ if (tagnum == 0xA002)
+ new_value = new_width; /* ExifImageWidth Tag */
+ else
+ new_value = new_height; /* ExifImageHeight Tag */
+ if (is_motorola) {
+ data[offset+2] = 0; /* Format = unsigned long (4 octets) */
+ data[offset+3] = 4;
+ data[offset+4] = 0; /* Number Of Components = 1 */
+ data[offset+5] = 0;
+ data[offset+6] = 0;
+ data[offset+7] = 1;
+ data[offset+8] = 0;
+ data[offset+9] = 0;
+ data[offset+10] = (JOCTET)((new_value >> 8) & 0xFF);
+ data[offset+11] = (JOCTET)(new_value & 0xFF);
+ } else {
+ data[offset+2] = 4; /* Format = unsigned long (4 octets) */
+ data[offset+3] = 0;
+ data[offset+4] = 1; /* Number Of Components = 1 */
+ data[offset+5] = 0;
+ data[offset+6] = 0;
+ data[offset+7] = 0;
+ data[offset+8] = (JOCTET)(new_value & 0xFF);
+ data[offset+9] = (JOCTET)((new_value >> 8) & 0xFF);
+ data[offset+10] = 0;
+ data[offset+11] = 0;
+ }
+ }
+ offset += 12;
+ } while (--number_of_tags);
+}
+#endif
+
+
+/* Adjust output image parameters as needed.
+ *
+ * This must be called after jpeg_copy_critical_parameters()
+ * and before jpeg_write_coefficients().
+ *
+ * The return value is the set of virtual coefficient arrays to be written
+ * (either the ones allocated by jtransform_request_workspace, or the
+ * original source data arrays). The caller will need to pass this value
+ * to jpeg_write_coefficients().
+ */
+
+GLOBAL(jvirt_barray_ptr *)
+jtransform_adjust_parameters (j_decompress_ptr srcinfo,
+ j_compress_ptr dstinfo,
+ jvirt_barray_ptr *src_coef_arrays,
+ jpeg_transform_info *info)
+{
+ /* If force-to-grayscale is requested, adjust destination parameters */
+ if (info->force_grayscale) {
+ /* First, ensure we have YCbCr or grayscale data, and that the source's
+ * Y channel is full resolution. (No reasonable person would make Y
+ * be less than full resolution, so actually copying with that case
+ * isn't worth extra code space. But we check it to avoid crashing.)
+ */
+ if (((dstinfo->jpeg_color_space == JCS_YCbCr &&
+ dstinfo->num_components == 3) ||
+ (dstinfo->jpeg_color_space == JCS_GRAYSCALE &&
+ dstinfo->num_components == 1)) &&
+ srcinfo->comp_info[0].h_samp_factor == srcinfo->max_h_samp_factor &&
+ srcinfo->comp_info[0].v_samp_factor == srcinfo->max_v_samp_factor) {
+ /* We use jpeg_set_colorspace to make sure subsidiary settings get fixed
+ * properly. Among other things, it sets the target h_samp_factor &
+ * v_samp_factor to 1, which typically won't match the source.
+ * We have to preserve the source's quantization table number, however.
+ */
+ int sv_quant_tbl_no = dstinfo->comp_info[0].quant_tbl_no;
+ jpeg_set_colorspace(dstinfo, JCS_GRAYSCALE);
+ dstinfo->comp_info[0].quant_tbl_no = sv_quant_tbl_no;
+ } else {
+ /* Sorry, can't do it */
+ ERREXIT(dstinfo, JERR_CONVERSION_NOTIMPL);
+ }
+ } else if (info->num_components == 1) {
+ /* For a single-component source, we force the destination sampling factors
+ * to 1x1, with or without force_grayscale. This is useful because some
+ * decoders choke on grayscale images with other sampling factors.
+ */
+ dstinfo->comp_info[0].h_samp_factor = 1;
+ dstinfo->comp_info[0].v_samp_factor = 1;
+ }
+
+ /* Correct the destination's image dimensions as necessary
+ * for rotate/flip, resize, and crop operations.
+ */
+#if JPEG_LIB_VERSION >= 70
+ dstinfo->jpeg_width = info->output_width;
+ dstinfo->jpeg_height = info->output_height;
+#endif
+
+ /* Transpose destination image parameters */
+ switch (info->transform) {
+ case JXFORM_TRANSPOSE:
+ case JXFORM_TRANSVERSE:
+ case JXFORM_ROT_90:
+ case JXFORM_ROT_270:
+#if JPEG_LIB_VERSION < 70
+ dstinfo->image_width = info->output_height;
+ dstinfo->image_height = info->output_width;
+#endif
+ transpose_critical_parameters(dstinfo);
+ break;
+ default:
+#if JPEG_LIB_VERSION < 70
+ dstinfo->image_width = info->output_width;
+ dstinfo->image_height = info->output_height;
+#endif
+ break;
+ }
+
+ /* Adjust Exif properties */
+ if (srcinfo->marker_list != NULL &&
+ srcinfo->marker_list->marker == JPEG_APP0+1 &&
+ srcinfo->marker_list->data_length >= 6 &&
+ GETJOCTET(srcinfo->marker_list->data[0]) == 0x45 &&
+ GETJOCTET(srcinfo->marker_list->data[1]) == 0x78 &&
+ GETJOCTET(srcinfo->marker_list->data[2]) == 0x69 &&
+ GETJOCTET(srcinfo->marker_list->data[3]) == 0x66 &&
+ GETJOCTET(srcinfo->marker_list->data[4]) == 0 &&
+ GETJOCTET(srcinfo->marker_list->data[5]) == 0) {
+ /* Suppress output of JFIF marker */
+ dstinfo->write_JFIF_header = FALSE;
+#if JPEG_LIB_VERSION >= 70
+ /* Adjust Exif image parameters */
+ if (dstinfo->jpeg_width != srcinfo->image_width ||
+ dstinfo->jpeg_height != srcinfo->image_height)
+ /* Align data segment to start of TIFF structure for parsing */
+ adjust_exif_parameters(srcinfo->marker_list->data + 6,
+ srcinfo->marker_list->data_length - 6,
+ dstinfo->jpeg_width, dstinfo->jpeg_height);
+#endif
+ }
+
+ /* Return the appropriate output data set */
+ if (info->workspace_coef_arrays != NULL)
+ return info->workspace_coef_arrays;
+ return src_coef_arrays;
+}
+
+
+/* Execute the actual transformation, if any.
+ *
+ * This must be called *after* jpeg_write_coefficients, because it depends
+ * on jpeg_write_coefficients to have computed subsidiary values such as
+ * the per-component width and height fields in the destination object.
+ *
+ * Note that some transformations will modify the source data arrays!
+ */
+
+GLOBAL(void)
+jtransform_execute_transform (j_decompress_ptr srcinfo,
+ j_compress_ptr dstinfo,
+ jvirt_barray_ptr *src_coef_arrays,
+ jpeg_transform_info *info)
+{
+ jvirt_barray_ptr *dst_coef_arrays = info->workspace_coef_arrays;
+
+ /* Note: conditions tested here should match those in switch statement
+ * in jtransform_request_workspace()
+ */
+ switch (info->transform) {
+ case JXFORM_NONE:
+ if (info->x_crop_offset != 0 || info->y_crop_offset != 0)
+ do_crop(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset,
+ src_coef_arrays, dst_coef_arrays);
+ break;
+ case JXFORM_FLIP_H:
+ if (info->y_crop_offset != 0 || info->slow_hflip)
+ do_flip_h(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset,
+ src_coef_arrays, dst_coef_arrays);
+ else
+ do_flip_h_no_crop(srcinfo, dstinfo, info->x_crop_offset,
+ src_coef_arrays);
+ break;
+ case JXFORM_FLIP_V:
+ do_flip_v(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset,
+ src_coef_arrays, dst_coef_arrays);
+ break;
+ case JXFORM_TRANSPOSE:
+ do_transpose(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset,
+ src_coef_arrays, dst_coef_arrays);
+ break;
+ case JXFORM_TRANSVERSE:
+ do_transverse(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset,
+ src_coef_arrays, dst_coef_arrays);
+ break;
+ case JXFORM_ROT_90:
+ do_rot_90(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset,
+ src_coef_arrays, dst_coef_arrays);
+ break;
+ case JXFORM_ROT_180:
+ do_rot_180(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset,
+ src_coef_arrays, dst_coef_arrays);
+ break;
+ case JXFORM_ROT_270:
+ do_rot_270(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset,
+ src_coef_arrays, dst_coef_arrays);
+ break;
+ }
+}
+
+/* jtransform_perfect_transform
+ *
+ * Determine whether lossless transformation is perfectly
+ * possible for a specified image and transformation.
+ *
+ * Inputs:
+ * image_width, image_height: source image dimensions.
+ * MCU_width, MCU_height: pixel dimensions of MCU.
+ * transform: transformation identifier.
+ * Parameter sources from initialized jpeg_struct
+ * (after reading source header):
+ * image_width = cinfo.image_width
+ * image_height = cinfo.image_height
+ * MCU_width = cinfo.max_h_samp_factor * cinfo.block_size
+ * MCU_height = cinfo.max_v_samp_factor * cinfo.block_size
+ * Result:
+ * TRUE = perfect transformation possible
+ * FALSE = perfect transformation not possible
+ * (may use custom action then)
+ */
+
+GLOBAL(boolean)
+jtransform_perfect_transform(JDIMENSION image_width, JDIMENSION image_height,
+ int MCU_width, int MCU_height,
+ JXFORM_CODE transform)
+{
+ boolean result = TRUE; /* initialize TRUE */
+
+ switch (transform) {
+ case JXFORM_FLIP_H:
+ case JXFORM_ROT_270:
+ if (image_width % (JDIMENSION) MCU_width)
+ result = FALSE;
+ break;
+ case JXFORM_FLIP_V:
+ case JXFORM_ROT_90:
+ if (image_height % (JDIMENSION) MCU_height)
+ result = FALSE;
+ break;
+ case JXFORM_TRANSVERSE:
+ case JXFORM_ROT_180:
+ if (image_width % (JDIMENSION) MCU_width)
+ result = FALSE;
+ if (image_height % (JDIMENSION) MCU_height)
+ result = FALSE;
+ break;
+ default:
+ break;
+ }
+
+ return result;
+}
+
+#endif /* TRANSFORMS_SUPPORTED */
+
+
+/* Setup decompression object to save desired markers in memory.
+ * This must be called before jpeg_read_header() to have the desired effect.
+ */
+
+GLOBAL(void)
+jcopy_markers_setup (j_decompress_ptr srcinfo, JCOPY_OPTION option)
+{
+#ifdef SAVE_MARKERS_SUPPORTED
+ int m;
+
+ /* Save comments except under NONE option */
+ if (option != JCOPYOPT_NONE) {
+ jpeg_save_markers(srcinfo, JPEG_COM, 0xFFFF);
+ }
+ /* Save all types of APPn markers iff ALL option */
+ if (option == JCOPYOPT_ALL) {
+ for (m = 0; m < 16; m++)
+ jpeg_save_markers(srcinfo, JPEG_APP0 + m, 0xFFFF);
+ }
+#else
+ (void) srcinfo; (void) option;
+#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.
+ */
+ (void)option;
+
+ 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/vcl/source/filter/jpeg/transupp.h b/vcl/source/filter/jpeg/transupp.h
new file mode 100644
index 000000000..a5d403335
--- /dev/null
+++ b/vcl/source/filter/jpeg/transupp.h
@@ -0,0 +1,212 @@
+/*
+ * transupp.h
+ *
+ * Copyright (C) 1997-2011, Thomas G. Lane, Guido Vollbeding.
+ * This file is part of the Independent JPEG Group's software.
+ * For conditions of distribution and use, see the accompanying README file.
+ *
+ * This file contains declarations for image transformation routines and
+ * other utility code used by the jpegtran sample application. These are
+ * NOT part of the core JPEG library. But we keep these routines separate
+ * from jpegtran.c to ease the task of maintaining jpegtran-like programs
+ * that have other user interfaces.
+ *
+ * NOTE: all the routines declared here have very specific requirements
+ * about when they are to be executed during the reading and writing of the
+ * source and destination files. See the comments in transupp.c, or see
+ * jpegtran.c for an example of correct usage.
+ */
+
+/* If you happen not to want the image transform support, disable it here */
+#ifndef TRANSFORMS_SUPPORTED
+#define TRANSFORMS_SUPPORTED 1 /* 0 disables transform code */
+#endif
+
+/*
+ * Although rotating and flipping data expressed as DCT coefficients is not
+ * hard, there is an asymmetry in the JPEG format specification for images
+ * whose dimensions aren't multiples of the iMCU size. The right and bottom
+ * image edges are padded out to the next iMCU boundary with junk data; but
+ * no padding is possible at the top and left edges. If we were to flip
+ * the whole image including the pad data, then pad garbage would become
+ * visible at the top and/or left, and real pixels would disappear into the
+ * pad margins --- perhaps permanently, since encoders & decoders may not
+ * bother to preserve DCT blocks that appear to be completely outside the
+ * nominal image area. So, we have to exclude any partial iMCUs from the
+ * basic transformation.
+ *
+ * Transpose is the only transformation that can handle partial iMCUs at the
+ * right and bottom edges completely cleanly. flip_h can flip partial iMCUs
+ * at the bottom, but leaves any partial iMCUs at the right edge untouched.
+ * Similarly flip_v leaves any partial iMCUs at the bottom edge untouched.
+ * The other transforms are defined as combinations of these basic transforms
+ * and process edge blocks in a way that preserves the equivalence.
+ *
+ * The "trim" option causes untransformable partial iMCUs to be dropped;
+ * this is not strictly lossless, but it usually gives the best-looking
+ * result for odd-size images. Note that when this option is active,
+ * the expected mathematical equivalences between the transforms may not hold.
+ * (For example, -rot 270 -trim trims only the bottom edge, but -rot 90 -trim
+ * followed by -rot 180 -trim trims both edges.)
+ *
+ * We also offer a lossless-crop option, which discards data outside a given
+ * image region but losslessly preserves what is inside. Like the rotate and
+ * flip transforms, lossless crop is restricted by the JPEG format: the upper
+ * left corner of the selected region must fall on an iMCU boundary. If this
+ * does not hold for the given crop parameters, we silently move the upper left
+ * corner up and/or left to make it so, simultaneously increasing the region
+ * dimensions to keep the lower right crop corner unchanged. (Thus, the
+ * output image covers at least the requested region, but may cover more.)
+ * The adjustment of the region dimensions may be optionally disabled.
+ *
+ * We also provide a lossless-resize option, which is kind of a lossless-crop
+ * operation in the DCT coefficient block domain - it discards higher-order
+ * coefficients and losslessly preserves lower-order coefficients of a
+ * sub-block.
+ *
+ * Rotate/flip transform, resize, and crop can be requested together in a
+ * single invocation. The crop is applied last --- that is, the crop region
+ * is specified in terms of the destination image after transform/resize.
+ *
+ * We also offer a "force to grayscale" option, which simply discards the
+ * chrominance channels of a YCbCr image. This is lossless in the sense that
+ * the luminance channel is preserved exactly. It's not the same kind of
+ * thing as the rotate/flip transformations, but it's convenient to handle it
+ * as part of this package, mainly because the transformation routines have to
+ * be aware of the option to know how many components to work on.
+ */
+
+/* Short forms of external names for systems with brain-damaged linkers. */
+
+#ifdef NEED_SHORT_EXTERNAL_NAMES
+#define jtransform_parse_crop_spec jTrParCrop
+#define jtransform_request_workspace jTrRequest
+#define jtransform_adjust_parameters jTrAdjust
+#define jtransform_execute_transform jTrExec
+#define jtransform_perfect_transform jTrPerfect
+#define jcopy_markers_setup jCMrkSetup
+#define jcopy_markers_execute jCMrkExec
+#endif /* NEED_SHORT_EXTERNAL_NAMES */
+
+/*
+ * Codes for supported types of image transformations.
+ */
+
+typedef enum {
+ JXFORM_NONE, /* no transformation */
+ JXFORM_FLIP_H, /* horizontal flip */
+ JXFORM_FLIP_V, /* vertical flip */
+ JXFORM_TRANSPOSE, /* transpose across UL-to-LR axis */
+ JXFORM_TRANSVERSE, /* transpose across UR-to-LL axis */
+ JXFORM_ROT_90, /* 90-degree clockwise rotation */
+ JXFORM_ROT_180, /* 180-degree rotation */
+ JXFORM_ROT_270 /* 270-degree clockwise (or 90 ccw) */
+} JXFORM_CODE;
+
+/*
+ * Codes for crop parameters, which can individually be unspecified,
+ * positive or negative for xoffset or yoffset,
+ * positive or forced for width or height.
+ */
+
+typedef enum {
+ JCROP_UNSET,
+ JCROP_POS,
+ JCROP_NEG,
+ JCROP_FORCE
+} JCROP_CODE;
+
+/*
+ * Transform parameters struct.
+ * NB: application must not change any elements of this struct after
+ * calling jtransform_request_workspace.
+ */
+
+typedef struct {
+ /* Options: set by caller */
+ JXFORM_CODE transform; /* image transform operator */
+ boolean perfect; /* if TRUE, fail if partial MCUs are requested */
+ boolean trim; /* if TRUE, trim partial MCUs as needed */
+ boolean force_grayscale; /* if TRUE, convert color image to grayscale */
+ boolean crop; /* if TRUE, crop source image */
+ boolean slow_hflip; /* For best performance, the JXFORM_FLIP_H transform
+ normally modifies the source coefficients in place.
+ Setting this to TRUE will instead use a slower,
+ double-buffered algorithm, which leaves the source
+ coefficients intact (necessary if other transformed
+ images must be generated from the same set of
+ coefficients. */
+
+ /* Crop parameters: application need not set these unless crop is TRUE.
+ * These can be filled in by jtransform_parse_crop_spec().
+ */
+ JDIMENSION crop_width; /* Width of selected region */
+ JCROP_CODE crop_width_set; /* (forced disables adjustment) */
+ JDIMENSION crop_height; /* Height of selected region */
+ JCROP_CODE crop_height_set; /* (forced disables adjustment) */
+ JDIMENSION crop_xoffset; /* X offset of selected region */
+ JCROP_CODE crop_xoffset_set; /* (negative measures from right edge) */
+ JDIMENSION crop_yoffset; /* Y offset of selected region */
+ JCROP_CODE crop_yoffset_set; /* (negative measures from bottom edge) */
+
+ /* Internal workspace: caller should not touch these */
+ int num_components; /* # of components in workspace */
+ jvirt_barray_ptr * workspace_coef_arrays; /* workspace for transformations */
+ JDIMENSION output_width; /* cropped destination dimensions */
+ JDIMENSION output_height;
+ JDIMENSION x_crop_offset; /* destination crop offsets measured in iMCUs */
+ JDIMENSION y_crop_offset;
+ int iMCU_sample_width; /* destination iMCU size */
+ int iMCU_sample_height;
+} jpeg_transform_info;
+
+#if TRANSFORMS_SUPPORTED
+
+/* Request any required workspace */
+EXTERN(boolean) jtransform_request_workspace
+ (j_decompress_ptr srcinfo, jpeg_transform_info *info);
+/* Adjust output image parameters */
+EXTERN(jvirt_barray_ptr *) jtransform_adjust_parameters
+ (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+ jvirt_barray_ptr *src_coef_arrays,
+ jpeg_transform_info *info);
+/* Execute the actual transformation, if any */
+EXTERN(void) jtransform_execute_transform
+ (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+ jvirt_barray_ptr *src_coef_arrays,
+ jpeg_transform_info *info);
+/* Determine whether lossless transformation is perfectly
+ * possible for a specified image and transformation.
+ */
+EXTERN(boolean) jtransform_perfect_transform
+ (JDIMENSION image_width, JDIMENSION image_height,
+ int MCU_width, int MCU_height,
+ JXFORM_CODE transform);
+
+/* jtransform_execute_transform used to be called
+ * jtransform_execute_transformation, but some compilers complain about
+ * routine names that long. This macro is here to avoid breaking any
+ * old source code that uses the original name...
+ */
+#define jtransform_execute_transformation jtransform_execute_transform
+
+#endif /* TRANSFORMS_SUPPORTED */
+
+/*
+ * Support for copying optional markers from source to destination file.
+ */
+
+typedef enum {
+ JCOPYOPT_NONE, /* copy no optional markers */
+ JCOPYOPT_COMMENTS, /* copy only comment (COM) markers */
+ JCOPYOPT_ALL /* copy all optional markers */
+} JCOPY_OPTION;
+
+
+/* Setup decompression object to save desired markers in memory */
+EXTERN(void) jcopy_markers_setup
+ (j_decompress_ptr srcinfo, JCOPY_OPTION option);
+/* Copy markers saved in the given source object to the destination object */
+EXTERN(void) jcopy_markers_execute
+ (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+ JCOPY_OPTION option);