[libcamera-devel] [PATCH 1/1] android: jpeg: Add a basic NV12 image scaler

Umang Jain email at uajain.com
Tue Sep 22 10:54:44 CEST 2020


Add a basic image scaler for NV12 frames being captured. The primary
use of this scaler will be to generate a thumbnail image to be embedded
as a part of EXIF metadata of the frame.

Signed-off-by: Umang Jain <email at uajain.com>
---
 src/android/jpeg/encoder_libjpeg.cpp |  10 ++
 src/android/jpeg/scaler.cpp          | 137 +++++++++++++++++++++++++++
 src/android/jpeg/scaler.h            |  32 +++++++
 src/android/meson.build              |   1 +
 4 files changed, 180 insertions(+)
 create mode 100644 src/android/jpeg/scaler.cpp
 create mode 100644 src/android/jpeg/scaler.h

diff --git a/src/android/jpeg/encoder_libjpeg.cpp b/src/android/jpeg/encoder_libjpeg.cpp
index 510613c..9ecf9b1 100644
--- a/src/android/jpeg/encoder_libjpeg.cpp
+++ b/src/android/jpeg/encoder_libjpeg.cpp
@@ -6,6 +6,7 @@
  */
 
 #include "encoder_libjpeg.h"
+#include "scaler.h"
 
 #include <fcntl.h>
 #include <iomanip>
@@ -214,6 +215,15 @@ int EncoderLibJpeg::encode(const FrameBuffer *source,
 	LOG(JPEG, Debug) << "JPEG Encode Starting:" << compress_.image_width
 			 << "x" << compress_.image_height;
 
+	Scaler scaler;
+	libcamera::Span<uint8_t> thumbnail;
+	scaler.configure(Size (compress_.image_width, compress_.image_height),
+			 /* \todo: Check for exact thumbnail size required by EXIF spec. */
+		         Size (compress_.image_width / 3, compress_.image_height / 3),
+			 pixelFormatInfo_);
+	scaler.scaleBuffer(source, thumbnail);
+	/* \todo: Write thumbnail as part of exifData. */
+
 	if (nv_)
 		compressNV(&frame);
 	else
diff --git a/src/android/jpeg/scaler.cpp b/src/android/jpeg/scaler.cpp
new file mode 100644
index 0000000..ff36ece
--- /dev/null
+++ b/src/android/jpeg/scaler.cpp
@@ -0,0 +1,137 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2020, Google Inc.
+ *
+ * scaler.cpp - Basic image scaler from NV12 to RGB24 format
+ */
+
+#include "scaler.h"
+
+#include <math.h>
+#include <string.h>
+
+#include "libcamera/internal/log.h"
+#include "libcamera/internal/file.h"
+
+#define RGBSHIFT                8
+#ifndef MAX
+#define MAX(a,b)                ((a)>(b)?(a):(b))
+#endif
+#ifndef MIN
+#define MIN(a,b)                ((a)<(b)?(a):(b))
+#endif
+#ifndef CLAMP
+#define CLAMP(a,low,high)       MAX((low),MIN((high),(a)))
+#endif
+#ifndef CLIP
+#define CLIP(x)                 CLAMP(x,0,255)
+#endif
+
+using namespace libcamera;
+
+LOG_DEFINE_CATEGORY(SCALER)
+
+Scaler::Scaler()
+	: sourceSize_(0, 0), targetSize_(0,0)
+{
+}
+
+Scaler::~Scaler()
+{
+}
+
+void Scaler::configure(Size sourceSize, Size targetSize, const PixelFormatInfo *pixelFormatInfo)
+{
+	sourceSize_ = sourceSize;
+	targetSize_ = targetSize;
+	pixelFormatInfo_ = pixelFormatInfo;
+}
+
+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++);
+}
+
+/* Handpicked from src/qcam/format_converter.cpp */
+static void yuv_to_rgb(int y, int u, int v, int *r, int *g, int *b)
+{
+	int c = y - 16;
+	int d = u - 128;
+	int e = v - 128;
+	*r = CLIP(( 298 * c           + 409 * e + 128) >> RGBSHIFT);
+	*g = CLIP(( 298 * c - 100 * d - 208 * e + 128) >> RGBSHIFT);
+	*b = CLIP(( 298 * c + 516 * d           + 128) >> RGBSHIFT);
+}
+
+int Scaler::scaleBuffer(const FrameBuffer *source, Span<uint8_t> &dest)
+{
+	MappedFrameBuffer frame(source, PROT_READ);
+	if (!frame.isValid()) {
+		LOG(SCALER, Error) << "Failed to map FrameBuffer : "
+				   << strerror(frame.error());
+		return frame.error();
+	}
+
+	if (strcmp(pixelFormatInfo_->name, "NV12") != 0) {
+		LOG (SCALER, Info) << "Source Buffer not in NV12 format, returning...";
+		return -1;
+	}
+
+	/* Image scaling block implementing nearest-neighbour algorithm. */
+	{
+		unsigned int cb_pos = 0;
+		unsigned int cr_pos = 1;
+		unsigned char *src = static_cast<unsigned char *>(frame.maps()[0].data());
+		unsigned char *src_c = src + sourceSize_.height * sourceSize_.width;
+		unsigned int stride = sourceSize_.width;
+		unsigned char *src_y, *src_cb, *src_cr;
+
+		unsigned int bpp = 3;
+		size_t dstSize = targetSize_.height * targetSize_.width * bpp;
+		unsigned char *dst = static_cast<unsigned char *>(malloc(dstSize));
+		unsigned char *destination = dst;
+		int r, g, b;
+
+		for (unsigned int y = 0; y < targetSize_.height; y++) {
+			int32_t sourceY = lround(sourceSize_.height * y / targetSize_.height);
+
+			for (unsigned int x = 0; x < targetSize_.width; x++) {
+				int32_t sourceX = lround(sourceSize_.width * x / targetSize_.width);
+
+				src_y = src + stride * sourceY + sourceX;
+				src_cb = src_c + (sourceY / 2) * stride + (sourceX / 2) * 2 + cb_pos;
+				src_cr = src_c + (sourceY / 2) * stride + (sourceX / 2) * 2 + cr_pos;
+
+				yuv_to_rgb(*src_y, *src_cb, *src_cr, &r, &g, &b);
+
+				destination[x * bpp + 0] = r;
+				destination[x * bpp + 1] = g;
+				destination[x * bpp + 2] = b;
+			}
+
+			destination = destination + bpp * targetSize_.width;
+		}
+
+		/* 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({ dst, dstSize });
+		LOG(SCALER, Info) << "Wrote " << ret << " bytes: " << targetSize_.width << "x" << targetSize_.height;
+
+		/* Write scaled pixels to dest */
+		dest = { dst, dstSize };
+	}
+
+	return 0;
+}
diff --git a/src/android/jpeg/scaler.h b/src/android/jpeg/scaler.h
new file mode 100644
index 0000000..c68a279
--- /dev/null
+++ b/src/android/jpeg/scaler.h
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2020, Google Inc.
+ *
+ * Basic image scaler from NV12 to RGB24 format
+ */
+#ifndef __ANDROID_JPEG_SCALER_H__
+#define __ANDROID_JPEG_SCALER_H__
+
+#include <libcamera/geometry.h>
+
+#include "libcamera/internal/buffer.h"
+#include "libcamera/internal/formats.h"
+
+class Scaler
+{
+public:
+	Scaler();
+	~Scaler();
+
+	void configure(libcamera::Size sourceSize, libcamera::Size targetSize,
+		       const libcamera::PixelFormatInfo *pixelFormatInfo);
+	int scaleBuffer(const libcamera::FrameBuffer *source, libcamera::Span<uint8_t> &dest);
+
+private:
+
+	libcamera::Size sourceSize_;
+	libcamera::Size targetSize_;
+	const libcamera::PixelFormatInfo *pixelFormatInfo_;
+};
+
+#endif /* __ANDROID_JPEG_SCALER_H__ */
diff --git a/src/android/meson.build b/src/android/meson.build
index 0293c20..aefb0da 100644
--- a/src/android/meson.build
+++ b/src/android/meson.build
@@ -22,6 +22,7 @@ android_hal_sources = files([
     'camera_ops.cpp',
     'jpeg/encoder_libjpeg.cpp',
     'jpeg/exif.cpp',
+    'jpeg/scaler.cpp',
 ])
 
 android_camera_metadata_sources = files([
-- 
2.25.1



More information about the libcamera-devel mailing list