blob: f186e03a4133f77a75b9bfd482d5bcc5be09da09 [file] [log] [blame]
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed 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
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define LOG_NDEBUG 0
#define LOG_TAG "EmulatedCamera_JPEGStub"
#include <YuvToJpegEncoder.h>
#include <cutils/log.h>
#include <errno.h>
#include <libyuv.h>
#include "ExifMetadataBuilder.h"
#include "JpegStub.h"
namespace {
bool GenerateThumbnail(const uint8_t* source_nv21, int source_width,
int source_height, int thumbnail_width,
int thumbnail_height, SkDynamicMemoryWStream* target) {
// We need to convert the image to Y'UV420SP to I420, which seems to be the
// only scalable format by the LibYUV.
// These formats are similar in their memory occupancy (both use about 3/2 of
// the total pixels).
int temp_y_size = source_width * source_height;
int temp_uv_size = temp_y_size / 4;
uint8_t* temp_y = (uint8_t*)malloc(temp_y_size + temp_uv_size + temp_uv_size);
uint8_t* temp_u = temp_y + temp_y_size;
uint8_t* temp_v = temp_u + temp_uv_size;
libyuv::NV12ToI420(source_nv21, source_width, source_nv21 + temp_y_size,
source_width, temp_y, source_width, temp_u,
source_width / 2, temp_v, source_width / 2, source_width,
source_height);
// Compute and allocate memory for thumbnail I420.
int thumb_y_size = thumbnail_width * thumbnail_height;
int thumb_uv_size = thumb_y_size / 4;
uint8_t* thumb_y =
(uint8_t*)malloc(thumb_y_size + thumb_uv_size + thumb_uv_size);
uint8_t* thumb_u = thumb_y + thumb_y_size;
uint8_t* thumb_v = thumb_u + thumb_uv_size;
libyuv::I420Scale(temp_y, source_width, temp_u, source_width / 2, temp_v,
source_width / 2, source_width, source_height, thumb_y,
thumbnail_width, thumb_u, thumbnail_width / 2, thumb_v,
thumbnail_width / 2, thumbnail_width, thumbnail_height,
libyuv::kFilterBilinear);
// Combine U and V components back to NV21 format.
// We can re-use temp_y buffer for our needs at this point.
for (int pix = 0; pix < thumb_uv_size; ++pix) {
temp_y[2 * pix] = thumb_v[pix];
temp_y[2 * pix + 1] = thumb_u[pix];
}
// Put the memory back. After this, the thumb_y points to beginning of NV21
// image which we can compress.
memcpy(thumb_u, temp_y, thumb_uv_size * 2);
// Compress image.
int strides[2] = {thumbnail_width, thumbnail_width};
int offsets[2] = {0, thumb_y_size};
Yuv420SpToJpegEncoder* encoder = new Yuv420SpToJpegEncoder(strides);
bool result = encoder->encode(target, thumb_y, thumbnail_width,
thumbnail_height, offsets, 90);
if (!result) {
ALOGE("%s: Thumbnail compression failed", __FUNCTION__);
}
delete (encoder);
free(thumb_y);
free(temp_y);
return result;
}
} // namespace
extern "C" void JpegStub_init(JpegStub* stub, int* strides) {
stub->mInternalEncoder = (void*)new Yuv420SpToJpegEncoder(strides);
stub->mInternalStream = (void*)new SkDynamicMemoryWStream();
stub->mExifBuilder = (void*)new android::ExifMetadataBuilder();
}
extern "C" void JpegStub_cleanup(JpegStub* stub) {
delete ((Yuv420SpToJpegEncoder*)stub->mInternalEncoder);
delete ((SkDynamicMemoryWStream*)stub->mInternalStream);
delete ((android::ExifMetadataBuilder*)stub->mExifBuilder);
}
extern "C" int JpegStub_compress(JpegStub* stub, const void* image, int quality,
const ImageMetadata* meta) {
void* pY = const_cast<void*>(image);
int offsets[2];
offsets[0] = 0;
offsets[1] = meta->mWidth * meta->mHeight;
Yuv420SpToJpegEncoder* encoder =
(Yuv420SpToJpegEncoder*)stub->mInternalEncoder;
SkDynamicMemoryWStream* stream =
(SkDynamicMemoryWStream*)stub->mInternalStream;
android::ExifMetadataBuilder* exif =
(android::ExifMetadataBuilder*)stub->mExifBuilder;
exif->SetWidth(meta->mWidth);
exif->SetHeight(meta->mHeight);
exif->SetDateTime(time(NULL));
if (meta->mLensFocalLength != -1)
exif->SetLensFocalLength(meta->mLensFocalLength);
if (meta->mGpsTimestamp != -1) {
exif->SetGpsLatitude(meta->mGpsLatitude);
exif->SetGpsLongitude(meta->mGpsLongitude);
exif->SetGpsAltitude(meta->mGpsAltitude);
exif->SetGpsDateTime(meta->mGpsTimestamp);
exif->SetGpsProcessingMethod(meta->mGpsProcessingMethod);
}
ALOGV("%s: Requested thumbnail size: %dx%d", __FUNCTION__,
meta->mThumbnailWidth, meta->mThumbnailHeight);
// Thumbnail requested?
if (meta->mThumbnailWidth > 0 && meta->mThumbnailHeight > 0) {
exif->SetThumbnailWidth(meta->mThumbnailWidth);
exif->SetThumbnailHeight(meta->mThumbnailHeight);
SkDynamicMemoryWStream* thumbnail = new SkDynamicMemoryWStream();
GenerateThumbnail((uint8_t*)pY, meta->mWidth, meta->mHeight,
meta->mThumbnailWidth, meta->mThumbnailHeight, thumbnail);
int thumbnail_size = thumbnail->bytesWritten();
void* thumbnail_data = malloc(thumbnail_size);
thumbnail->read(thumbnail_data, 0, thumbnail_size);
// Pass ownership to EXIF builder.
exif->SetThumbnail(thumbnail_data, thumbnail_size);
delete thumbnail;
}
exif->Build();
if (encoder->encode(stream, pY, meta->mWidth, meta->mHeight, offsets,
quality)) {
ALOGI("%s: Compressed JPEG: %d[%dx%d] -> %zu bytes", __FUNCTION__,
(meta->mWidth * meta->mHeight * 12) / 8, meta->mWidth, meta->mHeight,
stream->bytesWritten());
return 0;
} else {
ALOGE("%s: JPEG compression failed", __FUNCTION__);
return errno ? errno : EINVAL;
}
}
extern "C" void JpegStub_getCompressedImage(JpegStub* stub, void* buff) {
SkDynamicMemoryWStream* stream =
(SkDynamicMemoryWStream*)stub->mInternalStream;
android::ExifMetadataBuilder* exif =
(android::ExifMetadataBuilder*)stub->mExifBuilder;
char* target = (char*)buff;
memcpy(buff, exif->Buffer().data(), exif->Buffer().size());
target += exif->Buffer().size();
// Skip 0xFFD8 marker. This marker has already been included in Metadata.
stream->read(target, 2, stream->bytesWritten() - 2);
}
extern "C" size_t JpegStub_getCompressedSize(JpegStub* stub) {
SkDynamicMemoryWStream* stream =
(SkDynamicMemoryWStream*)stub->mInternalStream;
android::ExifMetadataBuilder* exif =
(android::ExifMetadataBuilder*)stub->mExifBuilder;
return stream->bytesWritten() + exif->Buffer().size() - 2;
}