diff options
Diffstat (limited to 'vcl/source/filter/jpeg/Exif.cxx')
-rw-r--r-- | vcl/source/filter/jpeg/Exif.cxx | 288 |
1 files changed, 288 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: */ |