unsigned int ImageWriterJPEG::writeRows()

in imagecore/formats/internal/jpeg.cpp [1029:1181]


unsigned int ImageWriterJPEG::writeRows(Image* source, unsigned int sourceRow, unsigned int numRows)
{
	if( Image::colorModelIsRGBA(source->getColorModel()) ) {

		ImageRGBA* sourceImage = source->asRGBA();

		uint8_t** rows = (uint8_t**)malloc(numRows * sizeof(uint8_t*));
		if( rows == NULL ) {
			return 0;
		}

		if( setjmp(m_JPEGError.jmp) ) {
			fprintf(stderr, "error during jpeg compress: %s", s_JPEGLastError);
			jpeg_destroy_compress(&m_JPEGCompress);
			free(rows);
			return 0;
		}

		unsigned int sourceHeight = sourceImage->getHeight();
		unsigned int sourcePitch = sourceImage->getPitch();
		uint8_t* sourceBuffer = sourceImage->lockRect(0, sourceRow, sourceImage->getWidth(), numRows, sourcePitch);
		SECURE_ASSERT(sourceRow + numRows <= sourceHeight);

#if !HAVE_RGBX
		unsigned int sourceWidth = sourceImage->getWidth();
		// Provided purely for compatibility for vanilla libjpeg, not recommended.
		uint8_t* jpegBuffer = (uint8_t*)malloc(sourceWidth * numRows * 3);
		if( jpegBuffer == NULL ) {
			free(rows);
			jpeg_destroy_compress(&m_JPEGCompress);
			return 0;
		}
		unsigned int jpegPitch = sourceWidth * 3;
		for (unsigned int y = 0; y < numRows; y++) {
			for (unsigned int x = 0; x < sourceWidth; x++) {
				uint8_t* out = jpegBuffer + y * jpegPitch + x * 3;
				uint8_t* in = sourceBuffer + y * sourcePitch + x * 4;
				out[0] = in[0];
				out[1] = in[1];
				out[2] = in[2];
			}
		}
#else
		uint8_t* jpegBuffer = sourceBuffer;
		unsigned int jpegPitch = sourcePitch;
#endif

		for( unsigned int i = 0; i < numRows; i++ ) {
			rows[i] = jpegBuffer + i * jpegPitch;
		}

		unsigned int currentRow = 0;
		while( currentRow < numRows ) {
			unsigned int numRowsWritten = jpeg_write_scanlines(&m_JPEGCompress, rows + currentRow, numRows - currentRow);
			if( numRowsWritten == 0 ) {
				break;
			}
			currentRow += numRowsWritten;
		}

		free(rows);

#if !HAVE_RGBX
		free(jpegBuffer);
#endif
		return numRows;
	} else if( Image::colorModelIsYUV(source->getColorModel()) ) {
		// TODO: Incremental writing
		ASSERT(sourceRow == 0);
		ASSERT(numRows == m_JPEGCompress.image_height);
		if( setjmp(m_JPEGError.jmp) ) {
			fprintf(stderr, "error during jpeg compress: %s", s_JPEGLastError);
			jpeg_destroy_compress(&m_JPEGCompress);
			return 0;
		}

		ImageYUV* yuvImage = source->asYUV();

		unsigned int minPitchY = align(DCTSIZE * 2, yuvImage->getPlaneY()->getWidth());
		unsigned int minPitchUV = align(DCTSIZE, yuvImage->getPlaneU()->getWidth());

		ImageYUV* sourceImage = yuvImage;
		ImageYUV* tempImage = NULL;
		// Image doesn't meet padding or range requirements, copy to a new buffer and correct.
		if( yuvImage->getRange() == kYUVRange_Compressed || (sourceImage->getPadding() < 16 && (m_WriteOptions & kWriteOption_AssumeMCUPaddingFilled) == 0) || yuvImage->getPlaneY()->getPitch() < minPitchY || yuvImage->getPlaneU()->getPitch() < minPitchUV ) {
			tempImage = ImageYUV::create(yuvImage->getWidth(), yuvImage->getHeight(), 16, 16);
			if( tempImage == NULL ) {
				return 0;
			}
			if( yuvImage->getRange() == kYUVRange_Compressed ) {
				yuvImage->expandRange(tempImage);
			} else {
				yuvImage->copy(tempImage);
			}
			sourceImage = tempImage;
			// Fill padding region of new image.
			sourceImage->getPlaneY()->fillPadding(kEdge_Right);
			sourceImage->getPlaneU()->fillPadding(kEdge_Right);
			sourceImage->getPlaneV()->fillPadding(kEdge_Right);
		} else if( (m_WriteOptions & kWriteOption_AssumeMCUPaddingFilled) == 0 ) {
			// Image was large enough and in the correct range, just fill the padding (unless it's been requested that we don't).
			// Use the existing padding region to satisfy jpeg_write_raw_data's requirement that we write
			// data aligned to the block size. Fill the edge padding data with data from the image (clamped)
			// to prevent any problems with the DCT.
			sourceImage->getPlaneY()->fillPadding(kEdge_Right);
			sourceImage->getPlaneU()->fillPadding(kEdge_Right);
			sourceImage->getPlaneV()->fillPadding(kEdge_Right);
		}

		SECURE_ASSERT(sourceImage->getPlaneY()->getPitch() >= minPitchY);
		SECURE_ASSERT(sourceImage->getPlaneU()->getPitch() >= minPitchUV);
		SECURE_ASSERT(sourceImage->getPlaneV()->getPitch() >= minPitchUV);

		ImagePlane8* planeY = sourceImage->getPlaneY();
		ImagePlane8* planeU = sourceImage->getPlaneU();
		ImagePlane8* planeV = sourceImage->getPlaneV();

		uint8_t* bufferY = (uint8_t*)planeY->getBytes();
		uint8_t* bufferU = (uint8_t*)planeU->getBytes();
		uint8_t* bufferV = (uint8_t*)planeV->getBytes();

		unsigned int pitchY = planeY->getPitch();
		unsigned int pitchU = planeU->getPitch();
		unsigned int pitchV = planeV->getPitch();

		unsigned int currentRowY = 0;
		unsigned int currentRowUV = 0;
		constexpr unsigned int rowStepY = DCTSIZE * 2;
		constexpr unsigned int rowStepUV = DCTSIZE;
		while( currentRowY < numRows ) {
			JSAMPROW rowsY[rowStepY];
			JSAMPROW rowsU[rowStepY];
			JSAMPROW rowsV[rowStepY];
			for( unsigned int y = 0; y < rowStepY; y++ ) {
				rowsY[y] = bufferY + pitchY * clamp(0, planeY->getHeight() - 1, y + currentRowY);
			}
			for( unsigned int y = 0; y < rowStepUV; y++ ) {
				rowsU[y] = bufferU + pitchU * clamp(0, planeU->getHeight() - 1, y + currentRowUV);
				rowsV[y] = bufferV + pitchV * clamp(0, planeV->getHeight() - 1, y + currentRowUV);
			}
			JSAMPARRAY rows[] = { rowsY, rowsU, rowsV };
			unsigned int numRowsWritten = jpeg_write_raw_data(&m_JPEGCompress, rows, rowStepY);
			currentRowY += numRowsWritten;
			currentRowUV += numRowsWritten / 2;
		}

		delete tempImage;

		return numRows;
	} else {
		return 0;
	}
}