Message ID | 20201008141038.83425-4-email@uajain.com |
---|---|
State | Superseded |
Delegated to: | Umang Jain |
Headers | show |
Series |
|
Related | show |
Hi Umang, On 08/10/2020 15:10, Umang Jain wrote: > Add a basic image thumbnailer for NV12 frames being captured. > It shall generate a thumbnail image to be embedded as a part of > EXIF metadata of the frame. The output of the thumbnail will still > be NV12. > > Signed-off-by: Umang Jain <email@uajain.com> > --- > src/android/jpeg/post_processor_jpeg.cpp | 11 ++ > src/android/jpeg/thumbnailer.cpp | 134 +++++++++++++++++++++++ > src/android/jpeg/thumbnailer.h | 34 ++++++ > src/android/meson.build | 1 + > 4 files changed, 180 insertions(+) > create mode 100644 src/android/jpeg/thumbnailer.cpp > create mode 100644 src/android/jpeg/thumbnailer.h > > diff --git a/src/android/jpeg/post_processor_jpeg.cpp b/src/android/jpeg/post_processor_jpeg.cpp > index eeb4e95..9076d04 100644 > --- a/src/android/jpeg/post_processor_jpeg.cpp > +++ b/src/android/jpeg/post_processor_jpeg.cpp > @@ -8,6 +8,7 @@ > #include "post_processor_jpeg.h" > > #include "exif.h" > +#include "thumbnailer.h" > > #include "../camera_device.h" > > @@ -286,6 +287,16 @@ int PostProcessorJpeg::encode(const FrameBuffer *source, > LOG(JPEG, Debug) << "JPEG Encode Starting:" << compress_.image_width > << "x" << compress_.image_height; > > + Thumbnailer th; > + libcamera::Span<uint8_t> thumbnail; > + th.configure(Size (compress_.image_width, compress_.image_height), > + pixelFormatInfo_->format); You could move the Thumbnailer into the PostProcessorJpeg class as a private, and call th.configure during PostProcessorJpeg::configure(). Potentially even allocate the destination thumbnail buffer there too? > + th.scaleBuffer(source, thumbnail); > + /* > + * \todo: Compress the thumbnail again encode() and set it in the > + * respective EXIF field. > + */ > + > if (nv_) > compressNV(&frame); > else > diff --git a/src/android/jpeg/thumbnailer.cpp b/src/android/jpeg/thumbnailer.cpp > new file mode 100644 > index 0000000..d01b4af > --- /dev/null > +++ b/src/android/jpeg/thumbnailer.cpp > @@ -0,0 +1,134 @@ > +/* SPDX-License-Identifier: GPL-2.0-or-later */ LGPL-2.1... > +/* > + * Copyright (C) 2020, Google Inc. > + * > + * thumbnailer.cpp - Basic image thumbnailer from NV12 > + */ > + > +#include "thumbnailer.h" > + > +#include <libcamera/formats.h> > + > +#include "libcamera/internal/file.h" > +#include "libcamera/internal/log.h" > + > +using namespace libcamera; > + > +LOG_DEFINE_CATEGORY(Thumbnailer) > + > +Thumbnailer::Thumbnailer() > + : validConfiguration_(false) > +{ > +} > + > +void Thumbnailer::configure(const Size &sourceSize, PixelFormat pixelFormat) > +{ > + sourceSize_ = sourceSize; > + pixelFormat_ = pixelFormat; > + > + if (pixelFormat_ != formats::NV12) { > + LOG (Thumbnailer, Error) << "Failed to configure: Pixel Format " > + << pixelFormat_.toString() << " unsupported."; > + return; > + } > + > + validConfiguration_ = true; > +} > + > +static std::string datetime() > +{ > + time_t rawtime; > + struct tm *timeinfo; > + char buffer[80]; > + static unsigned int milliseconds = 0; > + > + time(&rawtime); > + timeinfo = localtime(&rawtime); > + > + strftime(buffer, 80, "%d-%m-%Y.%H-%M-%S.", timeinfo); > + > + /* milliseconds is just a fast hack to ensure unique filenames */ > + return std::string(buffer) + std::to_string(milliseconds++); > +} > + > +/* > + * The Exif specification recommends the width of the thumbnail to be a > + * mutiple of 16 (section 4.8.1). Hence, compute the corresponding height s/mutiple/multiple/ > + * keeping the aspect ratio same as of the source. > + */ > +Size Thumbnailer::computeThumbnailSize() > +{ > + unsigned int targetHeight; > + unsigned int targetWidth = 160; > + > + targetHeight = targetWidth * sourceSize_.height / sourceSize_.width; > + > + if (targetHeight & 1) > + targetHeight++; > + > + return Size(targetWidth, targetHeight); > +} > + > +int Thumbnailer::scaleBuffer(const FrameBuffer *source, Span<uint8_t> &dest) > +{ > + MappedFrameBuffer frame(source, PROT_READ); > + if (!frame.isValid()) { > + LOG(Thumbnailer, Error) << "Failed to map FrameBuffer : " > + << strerror(frame.error()); > + return frame.error(); > + } > + > + if (!validConfiguration_) { > + LOG(Thumbnailer, Error) << "config is unconfigured or invalid."; > + return -1; > + } > + > + targetSize_ = computeThumbnailSize(); > + > + const unsigned int sw = sourceSize_.width; > + const unsigned int sh = sourceSize_.height; > + const unsigned int tw = targetSize_.width; > + const unsigned int th = targetSize_.height; > + > + /* Image scaling block implementing nearest-neighbour algorithm. */ > + unsigned char *src = static_cast<unsigned char *>(frame.maps()[0].data()); > + unsigned char *src_c = src + sh * sw; > + unsigned int cb_pos = 0; > + unsigned int cr_pos = 1; > + unsigned char *src_cb, *src_cr; > + > + size_t dstSize = (th * tw) + ((th/2) * tw); How about move this to a helper function: int Thumbnailer::outputSize() { if (!validConfiguration_) return -1 return targetSize_.height * targetSize_.width * 3 / 2 } > + unsigned char *destination = static_cast<unsigned char *>(malloc(dstSize)); And allocate this in the caller, passing in the buffer as a const Span reference or such. > + unsigned char *dst = destination; > + unsigned char *dst_c = destination + th * tw; > + > + for (unsigned int y = 0; y < th; y+=2) { > + unsigned int sourceY = (sh*y + th/2) / th; > + > + src_cb = src_c + (sourceY/2) * sw + cb_pos; > + src_cr = src_c + (sourceY/2) * sw + cr_pos; > + > + for (unsigned int x = 0; x < tw; x+=2) { > + unsigned int sourceX = (sw*x + tw/2) / tw; > + > + dst[y * tw + x] = src[sw * sourceY + sourceX]; > + dst[(y+1) * tw + x] = src[sw * (sourceY+1) + sourceX]; > + dst[y * tw + (x+1)] = src[sw * sourceY + (sourceX+1)]; > + dst[(y+1) * tw + (x+1)] = src[sw * (sourceY+1) + (sourceX+1)]; > + > + dst_c[(y/2) * tw + x + cb_pos] = src_cb[(sourceX/2) * 2]; > + dst_c[(y/2) * tw + x + cr_pos] = src_cr[(sourceX/2) * 2]; > + } > + } > + > + /* Helper code: Write the output pixels to a file so we can inspect */ > + File file("/tmp/" + datetime() + ".raw"); > + int32_t ret = file.open(File::WriteOnly); > + ret = file.write({ destination, dstSize }); > + LOG(Thumbnailer, Info) << "Wrote " << ret << " bytes: " << targetSize_.width << "x" << targetSize_.height; > + And of course that will be removed for a non-rfc... > + /* Write scaled pixels to dest */ > + dest = { destination, dstSize }; And this wouldnt' be needed if the dest is passed in. > + > + return 0; > +} > diff --git a/src/android/jpeg/thumbnailer.h b/src/android/jpeg/thumbnailer.h > new file mode 100644 > index 0000000..af3a194 > --- /dev/null > +++ b/src/android/jpeg/thumbnailer.h > @@ -0,0 +1,34 @@ > +/* SPDX-License-Identifier: GPL-2.0-or-later */ > +/* > + * Copyright (C) 2020, Google Inc. > + * > + * thumbnailer.h - Basic image thumbnailer from NV12 > + */ > +#ifndef __ANDROID_JPEG_THUMBNAILER_H__ > +#define __ANDROID_JPEG_THUMBNAILER_H__ > + > +#include <libcamera/geometry.h> > + > +#include "libcamera/internal/buffer.h" > +#include "libcamera/internal/formats.h" > + > +class Thumbnailer > +{ > +public: > + Thumbnailer(); > + > + void configure(const libcamera::Size &sourceSize, > + libcamera::PixelFormat pixelFormat); > + int scaleBuffer(const libcamera::FrameBuffer *source, libcamera::Span<uint8_t> &dest); > + > +private: > + libcamera::Size computeThumbnailSize(); > + > + libcamera::PixelFormat pixelFormat_; > + libcamera::Size sourceSize_; > + libcamera::Size targetSize_; > + > + bool validConfiguration_; > +}; > + > +#endif /* __ANDROID_JPEG_THUMBNAILER_H__ */ > diff --git a/src/android/meson.build b/src/android/meson.build > index 02b3b47..854005e 100644 > --- a/src/android/meson.build > +++ b/src/android/meson.build > @@ -23,6 +23,7 @@ android_hal_sources = files([ > 'camera_stream.cpp', > 'jpeg/exif.cpp', > 'jpeg/post_processor_jpeg.cpp', > + 'jpeg/thumbnailer.cpp', > ]) > > android_camera_metadata_sources = files([ >
Hello, On Fri, Oct 09, 2020 at 02:33:54PM +0100, Kieran Bingham wrote: > On 08/10/2020 15:10, Umang Jain wrote: > > Add a basic image thumbnailer for NV12 frames being captured. > > It shall generate a thumbnail image to be embedded as a part of > > EXIF metadata of the frame. The output of the thumbnail will still > > be NV12. > > > > Signed-off-by: Umang Jain <email@uajain.com> > > --- > > src/android/jpeg/post_processor_jpeg.cpp | 11 ++ > > src/android/jpeg/thumbnailer.cpp | 134 +++++++++++++++++++++++ > > src/android/jpeg/thumbnailer.h | 34 ++++++ > > src/android/meson.build | 1 + > > 4 files changed, 180 insertions(+) > > create mode 100644 src/android/jpeg/thumbnailer.cpp > > create mode 100644 src/android/jpeg/thumbnailer.h > > > > diff --git a/src/android/jpeg/post_processor_jpeg.cpp b/src/android/jpeg/post_processor_jpeg.cpp > > index eeb4e95..9076d04 100644 > > --- a/src/android/jpeg/post_processor_jpeg.cpp > > +++ b/src/android/jpeg/post_processor_jpeg.cpp > > @@ -8,6 +8,7 @@ > > #include "post_processor_jpeg.h" > > > > #include "exif.h" > > +#include "thumbnailer.h" > > > > #include "../camera_device.h" > > > > @@ -286,6 +287,16 @@ int PostProcessorJpeg::encode(const FrameBuffer *source, > > LOG(JPEG, Debug) << "JPEG Encode Starting:" << compress_.image_width > > << "x" << compress_.image_height; > > > > + Thumbnailer th; > > + libcamera::Span<uint8_t> thumbnail; > > + th.configure(Size (compress_.image_width, compress_.image_height), > > + pixelFormatInfo_->format); > > You could move the Thumbnailer into the PostProcessorJpeg class as a > private, and call th.configure during PostProcessorJpeg::configure(). > > Potentially even allocate the destination thumbnail buffer there too? Given that there's not much to configure (it's just about storing the size internally), how about passing the target size to the Thumbnailer::scaleBuffer() function ? configure() is useful for components that are configured once (potentially with expensive operations at configure time) and run multiple times, but here I don't think that's needed. > > + th.scaleBuffer(source, thumbnail); > > + /* > > + * \todo: Compress the thumbnail again encode() and set it in the > > + * respective EXIF field. > > + */ > > + > > if (nv_) > > compressNV(&frame); > > else > > diff --git a/src/android/jpeg/thumbnailer.cpp b/src/android/jpeg/thumbnailer.cpp > > new file mode 100644 > > index 0000000..d01b4af > > --- /dev/null > > +++ b/src/android/jpeg/thumbnailer.cpp > > @@ -0,0 +1,134 @@ > > +/* SPDX-License-Identifier: GPL-2.0-or-later */ > > LGPL-2.1... > > > +/* > > + * Copyright (C) 2020, Google Inc. > > + * > > + * thumbnailer.cpp - Basic image thumbnailer from NV12 > > + */ > > + > > +#include "thumbnailer.h" > > + > > +#include <libcamera/formats.h> > > + > > +#include "libcamera/internal/file.h" > > +#include "libcamera/internal/log.h" > > + > > +using namespace libcamera; > > + > > +LOG_DEFINE_CATEGORY(Thumbnailer) > > + > > +Thumbnailer::Thumbnailer() > > + : validConfiguration_(false) > > +{ > > +} > > + > > +void Thumbnailer::configure(const Size &sourceSize, PixelFormat pixelFormat) > > +{ > > + sourceSize_ = sourceSize; > > + pixelFormat_ = pixelFormat; > > + > > + if (pixelFormat_ != formats::NV12) { > > + LOG (Thumbnailer, Error) << "Failed to configure: Pixel Format " > > + << pixelFormat_.toString() << " unsupported."; > > + return; > > + } > > + > > + validConfiguration_ = true; > > +} > > + > > +static std::string datetime() > > +{ > > + time_t rawtime; > > + struct tm *timeinfo; > > + char buffer[80]; > > + static unsigned int milliseconds = 0; > > + > > + time(&rawtime); > > + timeinfo = localtime(&rawtime); > > + > > + strftime(buffer, 80, "%d-%m-%Y.%H-%M-%S.", timeinfo); > > + > > + /* milliseconds is just a fast hack to ensure unique filenames */ > > + return std::string(buffer) + std::to_string(milliseconds++); > > +} > > + > > +/* > > + * The Exif specification recommends the width of the thumbnail to be a > > + * mutiple of 16 (section 4.8.1). Hence, compute the corresponding height > > s/mutiple/multiple/ > > > + * keeping the aspect ratio same as of the source. > > + */ > > +Size Thumbnailer::computeThumbnailSize() > > +{ > > + unsigned int targetHeight; > > + unsigned int targetWidth = 160; > > + > > + targetHeight = targetWidth * sourceSize_.height / sourceSize_.width; > > + > > + if (targetHeight & 1) > > + targetHeight++; > > + > > + return Size(targetWidth, targetHeight); > > +} > > + > > +int Thumbnailer::scaleBuffer(const FrameBuffer *source, Span<uint8_t> &dest) > > +{ > > + MappedFrameBuffer frame(source, PROT_READ); > > + if (!frame.isValid()) { > > + LOG(Thumbnailer, Error) << "Failed to map FrameBuffer : " > > + << strerror(frame.error()); > > + return frame.error(); > > + } > > + > > + if (!validConfiguration_) { > > + LOG(Thumbnailer, Error) << "config is unconfigured or invalid."; > > + return -1; > > + } > > + > > + targetSize_ = computeThumbnailSize(); > > + > > + const unsigned int sw = sourceSize_.width; > > + const unsigned int sh = sourceSize_.height; > > + const unsigned int tw = targetSize_.width; > > + const unsigned int th = targetSize_.height; > > + > > + /* Image scaling block implementing nearest-neighbour algorithm. */ > > + unsigned char *src = static_cast<unsigned char *>(frame.maps()[0].data()); > > + unsigned char *src_c = src + sh * sw; > > + unsigned int cb_pos = 0; > > + unsigned int cr_pos = 1; > > + unsigned char *src_cb, *src_cr; > > + > > + size_t dstSize = (th * tw) + ((th/2) * tw); > > How about move this to a helper function: > > int Thumbnailer::outputSize() > { > if (!validConfiguration_) > return -1 > > return targetSize_.height * targetSize_.width * 3 / 2 > } It's only done once though, not sure it's worth it. > > + unsigned char *destination = static_cast<unsigned char *>(malloc(dstSize)); > > And allocate this in the caller, passing in the buffer as a const Span > reference or such. Wouldn't that make it more complex for the caller, that would need to retrieve the size, allocate the buffer and then call scaleBuffer() ? How about returning a std::vector from this function instead ? That would make buffer ownership clear, simplify the API for the caller, and with copy elision there will be no copy. Now that I've written this, I've read https://en.cppreference.com/w/cpp/language/copy_elision, and it seems copy elision is only mandatory for the compiler to implement when the operand to the return statement is a prvalue. In practice I expect the copy to always be eluded (I've tested it with recent g++ and clang++ with -O0 and -Os), but if we want a strong guarantee, we would need to pass the vector as an argument. > > + unsigned char *dst = destination; > > + unsigned char *dst_c = destination + th * tw; > > + > > + for (unsigned int y = 0; y < th; y+=2) { > > + unsigned int sourceY = (sh*y + th/2) / th; > > + > > + src_cb = src_c + (sourceY/2) * sw + cb_pos; > > + src_cr = src_c + (sourceY/2) * sw + cr_pos; > > + > > + for (unsigned int x = 0; x < tw; x+=2) { > > + unsigned int sourceX = (sw*x + tw/2) / tw; > > + > > + dst[y * tw + x] = src[sw * sourceY + sourceX]; > > + dst[(y+1) * tw + x] = src[sw * (sourceY+1) + sourceX]; > > + dst[y * tw + (x+1)] = src[sw * sourceY + (sourceX+1)]; > > + dst[(y+1) * tw + (x+1)] = src[sw * (sourceY+1) + (sourceX+1)]; > > + > > + dst_c[(y/2) * tw + x + cb_pos] = src_cb[(sourceX/2) * 2]; > > + dst_c[(y/2) * tw + x + cr_pos] = src_cr[(sourceX/2) * 2]; > > + } > > + } > > + > > + /* Helper code: Write the output pixels to a file so we can inspect */ > > + File file("/tmp/" + datetime() + ".raw"); > > + int32_t ret = file.open(File::WriteOnly); > > + ret = file.write({ destination, dstSize }); > > + LOG(Thumbnailer, Info) << "Wrote " << ret << " bytes: " << targetSize_.width << "x" << targetSize_.height; > > + > > And of course that will be removed for a non-rfc... > > > + /* Write scaled pixels to dest */ > > + dest = { destination, dstSize }; > > And this wouldnt' be needed if the dest is passed in. > > > + > > + return 0; > > +} > > diff --git a/src/android/jpeg/thumbnailer.h b/src/android/jpeg/thumbnailer.h > > new file mode 100644 > > index 0000000..af3a194 > > --- /dev/null > > +++ b/src/android/jpeg/thumbnailer.h > > @@ -0,0 +1,34 @@ > > +/* SPDX-License-Identifier: GPL-2.0-or-later */ > > +/* > > + * Copyright (C) 2020, Google Inc. > > + * > > + * thumbnailer.h - Basic image thumbnailer from NV12 > > + */ > > +#ifndef __ANDROID_JPEG_THUMBNAILER_H__ > > +#define __ANDROID_JPEG_THUMBNAILER_H__ > > + > > +#include <libcamera/geometry.h> > > + > > +#include "libcamera/internal/buffer.h" > > +#include "libcamera/internal/formats.h" > > + > > +class Thumbnailer > > +{ > > +public: > > + Thumbnailer(); > > + > > + void configure(const libcamera::Size &sourceSize, > > + libcamera::PixelFormat pixelFormat); > > + int scaleBuffer(const libcamera::FrameBuffer *source, libcamera::Span<uint8_t> &dest); > > + > > +private: > > + libcamera::Size computeThumbnailSize(); > > + > > + libcamera::PixelFormat pixelFormat_; > > + libcamera::Size sourceSize_; > > + libcamera::Size targetSize_; > > + > > + bool validConfiguration_; > > +}; > > + > > +#endif /* __ANDROID_JPEG_THUMBNAILER_H__ */ > > diff --git a/src/android/meson.build b/src/android/meson.build > > index 02b3b47..854005e 100644 > > --- a/src/android/meson.build > > +++ b/src/android/meson.build > > @@ -23,6 +23,7 @@ android_hal_sources = files([ > > 'camera_stream.cpp', > > 'jpeg/exif.cpp', > > 'jpeg/post_processor_jpeg.cpp', > > + 'jpeg/thumbnailer.cpp', > > ]) > > > > android_camera_metadata_sources = files([
diff --git a/src/android/jpeg/post_processor_jpeg.cpp b/src/android/jpeg/post_processor_jpeg.cpp index eeb4e95..9076d04 100644 --- a/src/android/jpeg/post_processor_jpeg.cpp +++ b/src/android/jpeg/post_processor_jpeg.cpp @@ -8,6 +8,7 @@ #include "post_processor_jpeg.h" #include "exif.h" +#include "thumbnailer.h" #include "../camera_device.h" @@ -286,6 +287,16 @@ int PostProcessorJpeg::encode(const FrameBuffer *source, LOG(JPEG, Debug) << "JPEG Encode Starting:" << compress_.image_width << "x" << compress_.image_height; + Thumbnailer th; + libcamera::Span<uint8_t> thumbnail; + th.configure(Size (compress_.image_width, compress_.image_height), + pixelFormatInfo_->format); + th.scaleBuffer(source, thumbnail); + /* + * \todo: Compress the thumbnail again encode() and set it in the + * respective EXIF field. + */ + if (nv_) compressNV(&frame); else diff --git a/src/android/jpeg/thumbnailer.cpp b/src/android/jpeg/thumbnailer.cpp new file mode 100644 index 0000000..d01b4af --- /dev/null +++ b/src/android/jpeg/thumbnailer.cpp @@ -0,0 +1,134 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2020, Google Inc. + * + * thumbnailer.cpp - Basic image thumbnailer from NV12 + */ + +#include "thumbnailer.h" + +#include <libcamera/formats.h> + +#include "libcamera/internal/file.h" +#include "libcamera/internal/log.h" + +using namespace libcamera; + +LOG_DEFINE_CATEGORY(Thumbnailer) + +Thumbnailer::Thumbnailer() + : validConfiguration_(false) +{ +} + +void Thumbnailer::configure(const Size &sourceSize, PixelFormat pixelFormat) +{ + sourceSize_ = sourceSize; + pixelFormat_ = pixelFormat; + + if (pixelFormat_ != formats::NV12) { + LOG (Thumbnailer, Error) << "Failed to configure: Pixel Format " + << pixelFormat_.toString() << " unsupported."; + return; + } + + validConfiguration_ = true; +} + +static std::string datetime() +{ + time_t rawtime; + struct tm *timeinfo; + char buffer[80]; + static unsigned int milliseconds = 0; + + time(&rawtime); + timeinfo = localtime(&rawtime); + + strftime(buffer, 80, "%d-%m-%Y.%H-%M-%S.", timeinfo); + + /* milliseconds is just a fast hack to ensure unique filenames */ + return std::string(buffer) + std::to_string(milliseconds++); +} + +/* + * The Exif specification recommends the width of the thumbnail to be a + * mutiple of 16 (section 4.8.1). Hence, compute the corresponding height + * keeping the aspect ratio same as of the source. + */ +Size Thumbnailer::computeThumbnailSize() +{ + unsigned int targetHeight; + unsigned int targetWidth = 160; + + targetHeight = targetWidth * sourceSize_.height / sourceSize_.width; + + if (targetHeight & 1) + targetHeight++; + + return Size(targetWidth, targetHeight); +} + +int Thumbnailer::scaleBuffer(const FrameBuffer *source, Span<uint8_t> &dest) +{ + MappedFrameBuffer frame(source, PROT_READ); + if (!frame.isValid()) { + LOG(Thumbnailer, Error) << "Failed to map FrameBuffer : " + << strerror(frame.error()); + return frame.error(); + } + + if (!validConfiguration_) { + LOG(Thumbnailer, Error) << "config is unconfigured or invalid."; + return -1; + } + + targetSize_ = computeThumbnailSize(); + + const unsigned int sw = sourceSize_.width; + const unsigned int sh = sourceSize_.height; + const unsigned int tw = targetSize_.width; + const unsigned int th = targetSize_.height; + + /* Image scaling block implementing nearest-neighbour algorithm. */ + unsigned char *src = static_cast<unsigned char *>(frame.maps()[0].data()); + unsigned char *src_c = src + sh * sw; + unsigned int cb_pos = 0; + unsigned int cr_pos = 1; + unsigned char *src_cb, *src_cr; + + size_t dstSize = (th * tw) + ((th/2) * tw); + unsigned char *destination = static_cast<unsigned char *>(malloc(dstSize)); + unsigned char *dst = destination; + unsigned char *dst_c = destination + th * tw; + + for (unsigned int y = 0; y < th; y+=2) { + unsigned int sourceY = (sh*y + th/2) / th; + + src_cb = src_c + (sourceY/2) * sw + cb_pos; + src_cr = src_c + (sourceY/2) * sw + cr_pos; + + for (unsigned int x = 0; x < tw; x+=2) { + unsigned int sourceX = (sw*x + tw/2) / tw; + + dst[y * tw + x] = src[sw * sourceY + sourceX]; + dst[(y+1) * tw + x] = src[sw * (sourceY+1) + sourceX]; + dst[y * tw + (x+1)] = src[sw * sourceY + (sourceX+1)]; + dst[(y+1) * tw + (x+1)] = src[sw * (sourceY+1) + (sourceX+1)]; + + dst_c[(y/2) * tw + x + cb_pos] = src_cb[(sourceX/2) * 2]; + dst_c[(y/2) * tw + x + cr_pos] = src_cr[(sourceX/2) * 2]; + } + } + + /* Helper code: Write the output pixels to a file so we can inspect */ + File file("/tmp/" + datetime() + ".raw"); + int32_t ret = file.open(File::WriteOnly); + ret = file.write({ destination, dstSize }); + LOG(Thumbnailer, Info) << "Wrote " << ret << " bytes: " << targetSize_.width << "x" << targetSize_.height; + + /* Write scaled pixels to dest */ + dest = { destination, dstSize }; + + return 0; +} diff --git a/src/android/jpeg/thumbnailer.h b/src/android/jpeg/thumbnailer.h new file mode 100644 index 0000000..af3a194 --- /dev/null +++ b/src/android/jpeg/thumbnailer.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2020, Google Inc. + * + * thumbnailer.h - Basic image thumbnailer from NV12 + */ +#ifndef __ANDROID_JPEG_THUMBNAILER_H__ +#define __ANDROID_JPEG_THUMBNAILER_H__ + +#include <libcamera/geometry.h> + +#include "libcamera/internal/buffer.h" +#include "libcamera/internal/formats.h" + +class Thumbnailer +{ +public: + Thumbnailer(); + + void configure(const libcamera::Size &sourceSize, + libcamera::PixelFormat pixelFormat); + int scaleBuffer(const libcamera::FrameBuffer *source, libcamera::Span<uint8_t> &dest); + +private: + libcamera::Size computeThumbnailSize(); + + libcamera::PixelFormat pixelFormat_; + libcamera::Size sourceSize_; + libcamera::Size targetSize_; + + bool validConfiguration_; +}; + +#endif /* __ANDROID_JPEG_THUMBNAILER_H__ */ diff --git a/src/android/meson.build b/src/android/meson.build index 02b3b47..854005e 100644 --- a/src/android/meson.build +++ b/src/android/meson.build @@ -23,6 +23,7 @@ android_hal_sources = files([ 'camera_stream.cpp', 'jpeg/exif.cpp', 'jpeg/post_processor_jpeg.cpp', + 'jpeg/thumbnailer.cpp', ]) android_camera_metadata_sources = files([
Add a basic image thumbnailer for NV12 frames being captured. It shall generate a thumbnail image to be embedded as a part of EXIF metadata of the frame. The output of the thumbnail will still be NV12. Signed-off-by: Umang Jain <email@uajain.com> --- src/android/jpeg/post_processor_jpeg.cpp | 11 ++ src/android/jpeg/thumbnailer.cpp | 134 +++++++++++++++++++++++ src/android/jpeg/thumbnailer.h | 34 ++++++ src/android/meson.build | 1 + 4 files changed, 180 insertions(+) create mode 100644 src/android/jpeg/thumbnailer.cpp create mode 100644 src/android/jpeg/thumbnailer.h