diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
commit | ed5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch) | |
tree | 7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /vcl/source/filter/jpeg | |
parent | Initial commit. (diff) | |
download | libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.tar.xz libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.zip |
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vcl/source/filter/jpeg')
-rw-r--r-- | vcl/source/filter/jpeg/Exif.cxx | 288 | ||||
-rw-r--r-- | vcl/source/filter/jpeg/Exif.hxx | 83 | ||||
-rw-r--r-- | vcl/source/filter/jpeg/JpegReader.cxx | 331 | ||||
-rw-r--r-- | vcl/source/filter/jpeg/JpegReader.hxx | 74 | ||||
-rw-r--r-- | vcl/source/filter/jpeg/JpegTransform.cxx | 42 | ||||
-rw-r--r-- | vcl/source/filter/jpeg/JpegTransform.hxx | 40 | ||||
-rw-r--r-- | vcl/source/filter/jpeg/JpegWriter.cxx | 265 | ||||
-rw-r--r-- | vcl/source/filter/jpeg/JpegWriter.hxx | 57 | ||||
-rw-r--r-- | vcl/source/filter/jpeg/jinclude.h | 79 | ||||
-rw-r--r-- | vcl/source/filter/jpeg/jpeg.cxx | 62 | ||||
-rw-r--r-- | vcl/source/filter/jpeg/jpeg.h | 67 | ||||
-rw-r--r-- | vcl/source/filter/jpeg/jpeg.hxx | 39 | ||||
-rw-r--r-- | vcl/source/filter/jpeg/jpegc.cxx | 520 | ||||
-rw-r--r-- | vcl/source/filter/jpeg/jpegcomp.h | 18 | ||||
-rw-r--r-- | vcl/source/filter/jpeg/transupp.c | 1570 | ||||
-rw-r--r-- | vcl/source/filter/jpeg/transupp.h | 212 |
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); |