Files
Mozilla/mozilla/modules/libpr0n/decoders/jpeg/nsJPEGEncoder.cpp
scott%scott-macgregor.org 2ee2592948 Bug #223909 --> Land the aviary 1.0 changes for supporting copy and paste of windows clipboard images into HTML mail
compose onto the 1.8 branch. Changes were re-reviewed for 1.8 by various folks including module owners: stuart, biesi, glazman

a=asa


git-svn-id: svn://10.0.0.236/branches/MOZILLA_1_8_BRANCH@179004 18797224-902f-48f8-a5cc-f745e15eee43
2005-08-25 22:35:49 +00:00

568 lines
20 KiB
C++
Executable File

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is the JPEG image encoder.
*
* The Initial Developer of the Original Code is
* Scott MacGregor <mscott@mozilla.org>.
* Portions created by the Initial Developer are Copyright (C) 2005
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#ifdef MOZ_LOGGING
// sorry, this has to be before the pre-compiled header
#define FORCE_PR_LOG /* Allow logging in the release build */
#endif
#include <stdio.h>
#include "nsJPEGEncoder.h"
#include "nsIClipboard.h"
#include "nsIComponentManager.h"
#include "nspr.h"
#include "nsCRT.h"
#include "ImageLogging.h"
#include "jerror.h"
#include "nsILocalFile.h"
#include "nsIOutputStream.h"
#include "nsNetUtil.h"
#include "nsDirectoryServiceDefs.h"
PRLogModuleInfo *gJPEGEncoderLog = PR_NewLogModule("JPEGEncoder");
#define OUTPUT_BUF_SIZE 4096 /* choose an efficiently fwrite'able size */
NS_IMPL_ISUPPORTS1(nsJPEGEncoder, imgIEncoder)
METHODDEF(boolean) empty_output_buffer (j_compress_ptr jd);
METHODDEF(void) init_destination (j_compress_ptr jd);
METHODDEF(void) term_destination (j_compress_ptr cinfo);
METHODDEF(void) my_error_exit (j_common_ptr cinfo);
// helper declarations for converting a bitmap to 4 byte RGB data
nsresult ConvertColorBitMap(unsigned char * aBitmapBuffer, PBITMAPINFO pBitMapInfo, unsigned char * aOutputBuffer);nsJPEGEncoder * getEncoderForCompressionInfoStruct(jpeg_compress_struct * cInfo);
struct bitFields {
PRUint32 red;
PRUint32 green;
PRUint32 blue;
PRUint8 redLeftShift;
PRUint8 redRightShift;
PRUint8 greenLeftShift;
PRUint8 greenRightShift;
PRUint8 blueLeftShift;
PRUint8 blueRightShift;
};
void CalcBitShift(bitFields * aColorMask);
/*
* Implementation of a JPEG destination manager object
*/
typedef struct {
/* public fields; must be first in this struct! */
struct jpeg_destination_mgr pub;
nsJPEGEncoder *encoder; // weak reference
} encoder_destination_mgr;
nsJPEGEncoder::nsJPEGEncoder()
{
mBuffer = nsnull;
mBufferLen = mBufferSize = 0;
memset(&mInfo, 0, sizeof(jpeg_compress_struct));
}
nsJPEGEncoder::~nsJPEGEncoder()
{
}
nsresult nsJPEGEncoder::initCompressionInfo()
{
/* We set up the normal JPEG error routines, then override error_exit. */
mInfo.err = jpeg_std_error(&mErr.pub);
mErr.pub.error_exit = my_error_exit;
/* Establish the setjmp return context for my_error_exit to use. */
if (setjmp(mErr.setjmp_buffer))
{
/* If we get here, the JPEG code has signaled an error.
* We need to clean up the JPEG object, close the input file, and return.
*/
return NS_ERROR_FAILURE;
}
jpeg_create_compress(&mInfo);
encoder_destination_mgr * destinationManager;
// first, setup the destination manager
if (mInfo.dest == NULL)
{
destinationManager = PR_NEWZAP(encoder_destination_mgr);
if (!destinationManager)
return NS_ERROR_OUT_OF_MEMORY;
mInfo.dest = NS_REINTERPRET_CAST(struct jpeg_destination_mgr *, destinationManager);
}
/* Setup callback functions. */
destinationManager->pub.init_destination = init_destination ;
destinationManager->pub.empty_output_buffer = empty_output_buffer;
destinationManager->pub.term_destination = term_destination;
destinationManager->encoder = this; // note the lack of a reference here. We own the lifetime of the destinationManager object
return NS_OK;
}
nsJPEGEncoder * getEncoderForCompressionInfoStruct(jpeg_compress_struct * cInfo)
{
if (cInfo && cInfo->dest)
{
encoder_destination_mgr *destinationManager = NS_REINTERPRET_CAST(encoder_destination_mgr *, cInfo->dest);
return destinationManager->encoder;
}
return NULL;
}
/** imgIEncoder methods **/
NS_IMETHODIMP nsJPEGEncoder::EncodeClipboardImage(nsIClipboardImage * aClipboardImage, nsIFile ** aImageFile)
{
// this is windows only....
STGMEDIUM stm;
nsresult rv = aClipboardImage->GetNativeImage( (void *) &stm);
NS_ENSURE_SUCCESS(rv, rv);
// test...try writing the bitmap to a file
nsCOMPtr<nsIFile> fileToUse;
NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(fileToUse));
fileToUse->Append(NS_LITERAL_STRING("moz-screenshot.jpg"));
nsCOMPtr<nsILocalFile> path = do_QueryInterface(fileToUse);
path->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
rv = NS_NewLocalFileOutputStream(getter_AddRefs(mOutputStream), path);
NS_ENSURE_SUCCESS(rv, rv);
// It is critical that we do not return early in this routine from here on out.
// We must release our lock and release stm before exiting
HGLOBAL hGlobal = stm.hGlobal;
BYTE * pGlobal = (BYTE *)::GlobalLock (hGlobal);
BITMAPINFO * bitMapInfo = (BITMAPINFO *) pGlobal;
rv = initCompressionInfo();
if (NS_SUCCEEDED(rv))
{
mInfo.in_color_space = JCS_RGB;
mInfo.input_components = 3;
mInfo.data_precision = 8;
mInfo.image_width = (JDIMENSION) bitMapInfo->bmiHeader.biWidth;
mInfo.image_height = (JDIMENSION) bitMapInfo->bmiHeader.biHeight;
unsigned char * rgbData = (unsigned char *) malloc (bitMapInfo->bmiHeader.biWidth * bitMapInfo->bmiHeader.biHeight * 3 /* RGB */);
if (rgbData)
{
rv = ConvertColorBitMap((unsigned char *) (pGlobal + bitMapInfo->bmiHeader.biSize), bitMapInfo, rgbData);
if (NS_SUCCEEDED(rv))
{
jpeg_set_defaults(&mInfo);
if ( bitMapInfo->bmiHeader.biXPelsPerMeter > 0 && bitMapInfo->bmiHeader.biYPelsPerMeter > 0)
{
/* Set JFIF density parameters from the BMP data */
mInfo.X_density = (UINT16) ( bitMapInfo->bmiHeader.biXPelsPerMeter/100); /* 100 cm per meter */
mInfo.Y_density = (UINT16) ( bitMapInfo->bmiHeader.biYPelsPerMeter/100);
mInfo.density_unit = 2; /* dots/cm */
}
jpeg_start_compress(&mInfo, TRUE);
PRInt32 row_stride = bitMapInfo->bmiHeader.biWidth * 3;
JSAMPROW row_pointer[1];
while (mInfo.next_scanline < mInfo.image_height)
{
row_pointer[0] = &rgbData[mInfo.next_scanline * row_stride];
jpeg_write_scanlines(&mInfo, row_pointer, 1);
}
jpeg_finish_compress(&mInfo);
}
free(rgbData);
} // if rgbdata
jpeg_destroy_compress(&mInfo);
}
// close the output file stream. we are done with it
mOutputStream->Close();
// return the file URL to the JPG
NS_IF_ADDREF(*aImageFile = fileToUse);
::GlobalUnlock (hGlobal); // release our lock on the bitmap
aClipboardImage->ReleaseNativeImage( (void *) &stm);
return NS_OK;
}
void InvertRows(unsigned char * aInitialBuffer, PRUint32 sizeOfBuffer, PRUint32 numBytesPerRow)
{
if (!numBytesPerRow)
return;
PRUint32 numRows = sizeOfBuffer / numBytesPerRow;
void * temporaryRowHolder = (void *) nsMemory::Alloc(numBytesPerRow);
PRUint32 currentRow = 0;
PRUint32 lastRow = (numRows - 1) * numBytesPerRow;
while (currentRow < lastRow)
{
// store the current row into a temporary buffer
memcpy(temporaryRowHolder, (void *) &aInitialBuffer[currentRow], numBytesPerRow);
memcpy((void *) &aInitialBuffer[currentRow], (void *)&aInitialBuffer[lastRow], numBytesPerRow);
memcpy((void *) &aInitialBuffer[lastRow], temporaryRowHolder, numBytesPerRow);
lastRow -= numBytesPerRow;
currentRow += numBytesPerRow;
}
nsMemory::Free(temporaryRowHolder);
}
nsresult ConvertColorBitMap(unsigned char * buffer, PBITMAPINFO pBitMapInfo, unsigned char * outBuffer)
{
PR_LOG(gJPEGEncoderLog, PR_LOG_ALWAYS, ("nsJPEGEncoder::ConvertColorBitMap"));
PRUint8 bitCount = pBitMapInfo->bmiHeader.biBitCount;
PRUint32 imageSize = pBitMapInfo->bmiHeader.biSizeImage; // may be zero for BI_RGB bitmaps which means we need to calculate by hand
PRUint32 bytesPerPixel = bitCount / 8;
if (bitCount <= 4)
bytesPerPixel = 1;
// rows are DWORD aligned. Calculate how many real bytes are in each row in the bitmap. This number won't
// correspond to biWidth.
PRUint32 rowSize = (bitCount * pBitMapInfo->bmiHeader.biWidth + 7) / 8; // +7 to round up
if (rowSize % 4)
rowSize += (4 - (rowSize % 4)); // Pad to DWORD Boundary
// if our buffer includes a color map, skip over it
if (bitCount <= 8)
{
PRInt32 bytesToSkip = (pBitMapInfo->bmiHeader.biClrUsed ? pBitMapInfo->bmiHeader.biClrUsed : (1 << bitCount) ) * sizeof(RGBQUAD);
buffer += bytesToSkip;
}
bitFields colorMasks; // only used if biCompression == BI_BITFIELDS
if (pBitMapInfo->bmiHeader.biCompression == BI_BITFIELDS)
{
// color table consists of 3 DWORDS containing the color masks...
colorMasks.red = (*((PRUint32*)&(pBitMapInfo->bmiColors[0])));
colorMasks.green = (*((PRUint32*)&(pBitMapInfo->bmiColors[1])));
colorMasks.blue = (*((PRUint32*)&(pBitMapInfo->bmiColors[2])));
CalcBitShift(&colorMasks);
buffer += 3 * sizeof(DWORD);
}
else if (pBitMapInfo->bmiHeader.biCompression == BI_RGB && !imageSize) // BI_RGB can have a size of zero which means we figure it out
{
// XXX: note use rowSize here and not biWidth. rowSize accounts for the DWORD padding for each row
imageSize = rowSize * pBitMapInfo->bmiHeader.biHeight;
}
// dump out some log information about the bit map info struct
PR_LOG(gJPEGEncoderLog, PR_LOG_ALWAYS, ("nsJPEGEncoder::ConvertColorBitMap biBitCount: %u", bitCount));
PR_LOG(gJPEGEncoderLog, PR_LOG_ALWAYS, ("nsJPEGEncoder::ConvertColorBitMap rowSize: %u", rowSize));
PR_LOG(gJPEGEncoderLog, PR_LOG_ALWAYS, ("nsJPEGEncoder::ConvertColorBitMap biCompression: %u", pBitMapInfo->bmiHeader.biCompression));
PR_LOG(gJPEGEncoderLog, PR_LOG_ALWAYS, ("nsJPEGEncoder::ConvertColorBitMap biWidth: %u", pBitMapInfo->bmiHeader.biWidth));
PR_LOG(gJPEGEncoderLog, PR_LOG_ALWAYS, ("nsJPEGEncoder::ConvertColorBitMap biHeight: %u", pBitMapInfo->bmiHeader.biHeight));
PR_LOG(gJPEGEncoderLog, PR_LOG_ALWAYS, ("nsJPEGEncoder::ConvertColorBitMap biClrUsed: %u", pBitMapInfo->bmiHeader.biClrUsed));
InvertRows(buffer, imageSize, rowSize);
PR_LOG(gJPEGEncoderLog, PR_LOG_ALWAYS, ("nsJPEGEncoder::ConvertColorBitMap bytesPerPixel: %u", bytesPerPixel));
PR_LOG(gJPEGEncoderLog, PR_LOG_ALWAYS, ("nsJPEGEncoder::ConvertColorBitMap calculated imageSize: %u", imageSize));
PR_LOG(gJPEGEncoderLog, PR_LOG_ALWAYS, ("nsJPEGEncoder::ConvertColorBitMap biSizeImage: %u", pBitMapInfo->bmiHeader.biSizeImage));
if (!pBitMapInfo->bmiHeader.biCompression || pBitMapInfo->bmiHeader.biCompression == BI_BITFIELDS)
{
PRUint32 index = 0;
PRUint32 writeIndex = 0;
unsigned char redValue, greenValue, blueValue;
PRUint8 colorTableEntry = 0;
PRInt8 bit; // used for grayscale bitmaps where each bit is a pixel
PRUint32 numPixelsLeftInRow = pBitMapInfo->bmiHeader.biWidth; // how many more pixels do we still need to read for the current row
PRUint32 pos = 0;
while (index < imageSize)
{
switch (bitCount)
{
case 1:
for (bit = 7; bit >= 0 && numPixelsLeftInRow; bit--)
{
colorTableEntry = (buffer[index] >> bit) & 1;
outBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbRed;
outBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbGreen;
outBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbBlue;
numPixelsLeftInRow--;
}
pos += 1;
break;
case 4:
{
// each buffer[index] entry contains data for two pixels.
// read the first pixel
colorTableEntry = buffer[index] >> 4;
outBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbRed;
outBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbGreen;
outBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbBlue;
numPixelsLeftInRow--;
if (numPixelsLeftInRow) // now read the second pixel
{
colorTableEntry = buffer[index] & 0xF;
outBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbRed;
outBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbGreen;
outBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbBlue;
numPixelsLeftInRow--;
}
pos += 1;
}
break;
case 8:
outBuffer[writeIndex++] = pBitMapInfo->bmiColors[buffer[index]].rgbRed;
outBuffer[writeIndex++] = pBitMapInfo->bmiColors[buffer[index]].rgbGreen;
outBuffer[writeIndex++] = pBitMapInfo->bmiColors[buffer[index]].rgbBlue;
numPixelsLeftInRow--;
pos += 1;
break;
case 16:
{
PRUint16 num = 0;
num = (PRUint8) buffer[index+1];
num <<= 8;
num |= (PRUint8) buffer[index];
redValue = ((PRUint32) (((float)(num & 0xf800) / 0xf800) * 0xFF0000) & 0xFF0000)>> 16;
greenValue = ((PRUint32)(((float)(num & 0x07E0) / 0x07E0) * 0x00FF00) & 0x00FF00)>> 8;
blueValue = ((PRUint32)(((float)(num & 0x001F) / 0x001F) * 0x0000FF) & 0x0000FF);
// now we have the right RGB values...
outBuffer[writeIndex++] = redValue;
outBuffer[writeIndex++] = greenValue;
outBuffer[writeIndex++] = blueValue;
numPixelsLeftInRow--;
pos += 2;
}
break;
case 32:
case 24:
if (pBitMapInfo->bmiHeader.biCompression == BI_BITFIELDS)
{
PRUint32 val = *((PRUint32*) (buffer + index) );
outBuffer[writeIndex++] = (val & colorMasks.red) >> colorMasks.redRightShift << colorMasks.redLeftShift;
outBuffer[writeIndex++] = (val & colorMasks.green) >> colorMasks.greenRightShift << colorMasks.greenLeftShift;
outBuffer[writeIndex++] = (val & colorMasks.blue) >> colorMasks.blueRightShift << colorMasks.blueLeftShift;
numPixelsLeftInRow--;
pos += 4; // we read in 4 bytes of data in order to process this pixel
}
else
{
outBuffer[writeIndex++] = buffer[index+2];
outBuffer[writeIndex++] = buffer[index+1];
outBuffer[writeIndex++] = buffer[index];
numPixelsLeftInRow--;
pos += bytesPerPixel; // 3 bytes for 24 bit data, 4 bytes for 32 bit data (we skip over the 4th byte)...
}
break;
default:
// This is probably the wrong place to check this...
return NS_ERROR_FAILURE;
}
index += bytesPerPixel; // increment our loop counter
if (!numPixelsLeftInRow)
{
if (rowSize != pos)
{
// advance index to skip over remaining padding bytes
index += (rowSize - pos);
}
numPixelsLeftInRow = pBitMapInfo->bmiHeader.biWidth;
pos = 0;
}
} // while we still have bytes to process
}
PR_LOG(gJPEGEncoderLog, PR_LOG_ALWAYS, ("exiting nsJPEGEncoder::ConvertColorBitMap"));
return NS_OK;
}
static void calcBitmask(PRUint32 aMask, PRUint8& aBegin, PRUint8& aLength)
{
// find the rightmost 1
PRUint8 pos;
PRBool started = PR_FALSE;
aBegin = aLength = 0;
for (pos = 0; pos <= 31; pos++)
{
if (!started && (aMask & (1 << pos)))
{
aBegin = pos;
started = PR_TRUE;
}
else if (started && !(aMask & (1 << pos)))
{
aLength = pos - aBegin;
break;
}
}
}
void CalcBitShift(bitFields * aColorMask)
{
PRUint8 begin, length;
// red
calcBitmask(aColorMask->red, begin, length);
aColorMask->redRightShift = begin;
aColorMask->redLeftShift = 8 - length;
// green
calcBitmask(aColorMask->green, begin, length);
aColorMask->greenRightShift = begin;
aColorMask->greenLeftShift = 8 - length;
// blue
calcBitmask(aColorMask->blue, begin, length);
aColorMask->blueRightShift = begin;
aColorMask->blueLeftShift = 8 - length;
}
/******************************************************************************/
/* data destination manager method
Initialize the destination buffer the JPEG library should write compressed bits into
*/
METHODDEF(void)
init_destination (j_compress_ptr jd)
{
nsJPEGEncoder * encoder = getEncoderForCompressionInfoStruct(jd);
if (!encoder)
return;
if (!encoder->mBuffer)
{
encoder->mBuffer = (JOCTET *)PR_Malloc(OUTPUT_BUF_SIZE);
encoder->mBufferSize = OUTPUT_BUF_SIZE;
}
jd->dest->next_output_byte = encoder->mBuffer;
jd->dest->free_in_buffer = OUTPUT_BUF_SIZE;
}
// called by the JPEG library when our working output buffer is full. We need to take the bits and write
// them to our destination stream. return true if buffer was dumped successfully otherwise return
METHODDEF(boolean) empty_output_buffer (j_compress_ptr cinfo)
{
nsJPEGEncoder * encoder = getEncoderForCompressionInfoStruct(cinfo);
if (!encoder)
return PR_TRUE;
PRUint32 written;
encoder->mOutputStream->Write((const char*) encoder->mBuffer, encoder->mBufferSize, &written);
// XXX: what do we do if we did write the # of bytes we thought we should write?
// now reset the jpeg pointers to the start of the buffer
cinfo->dest->next_output_byte = encoder->mBuffer;
cinfo->dest->free_in_buffer = OUTPUT_BUF_SIZE;
return PR_TRUE;
}
/* Terminate destination --- called by jpeg_finish_compress()() after all
* data has been written to clean up JPEG destination manager. NOT called by
* jpeg_abort() or jpeg_destroy().
*/
METHODDEF(void) term_destination (j_compress_ptr cinfo)
{
nsJPEGEncoder * encoder = getEncoderForCompressionInfoStruct(cinfo);
if (!encoder)
return;
// flush whatever is left in the buffer
PRUint32 written;
encoder->mOutputStream->Write((const char*) encoder->mBuffer, encoder->mBufferSize - cinfo->dest->free_in_buffer , &written);
// if we were an asynch process making calls back to an observer, we would
// make the notification saying we were all done in this method. Since we are blocking,
// do nothing.
}
/* Override the standard error method in the IJG JPEG decoder code.
*/
METHODDEF(void) my_error_exit (j_common_ptr cinfo)
{
nsresult error_code;
encoder_error_mgr *err = (encoder_error_mgr *) cinfo->err;
/* Convert error to a browser error code */
switch (cinfo->err->msg_code) {
case JERR_OUT_OF_MEMORY:
error_code = NS_ERROR_OUT_OF_MEMORY;
default:
error_code = NS_ERROR_FAILURE;
}
#ifdef DEBUG
char buffer[JMSG_LENGTH_MAX];
/* Create the message */
(*cinfo->err->format_message) (cinfo, buffer);
fprintf(stderr, "JPEG decoding error:\n%s\n", buffer);
#endif
/* Return control to the setjmp point. */
longjmp(err->setjmp_buffer, error_code);
}