Hi,

I've noticed there is currently no capture-preview support for CHDK,
although some stubs are present in the code. Of course, remote capture
without live view isn't that useful. So here's a patch, which also
updates chdk_live_view.h to the recent version. It supports either
jpeg (needs libjpeg) or ppm output.

Key issue with CHDK live view is that it usually produces a more or less
distorted picture with a wrong aspect ratio (typically 3:2 instead of
4:3). That's due to an internally used representation with "non-square"
pixels, so it seems impossible to do anything with it without
involving a scaling algorithm. Still it is better than nothing.

-- 
Regards,
Alexey Kryukov <anagnost at yandex dot ru>

Moscow State University
Faculty of History
diff -ubE ptp2.orig/chdk.c ptp2/chdk.c
--- ptp2.orig/chdk.c	2018-03-22 13:54:17.509009162 +0300
+++ ptp2/chdk.c	2018-03-23 13:22:35.392712627 +0300
@@ -27,6 +27,10 @@
 #include <stdarg.h>
 #include <time.h>
 
+#ifdef HAVE_LIBJPEG
+#  include <jpeglib.h>
+#endif
+
 #include <gphoto2/gphoto2-library.h>
 #include <gphoto2/gphoto2-port-log.h>
 #include <gphoto2/gphoto2-setting.h>
@@ -1152,6 +1156,228 @@
         return ret;
 }
 
+#ifdef HAVE_LIBJPEG
+static void yuv_live_to_jpeg(unsigned char *p_yuv,
+			     int buf_width, int width, int height,
+			     int fb_type, CameraFile *file
+) {
+	struct		jpeg_compress_struct cinfo;
+	struct		jpeg_error_mgr jerr;
+	JSAMPROW	row_ptr[1];
+	uint8_t		*outbuf = NULL, *tmprowbuf = NULL;
+	uint64_t	outlen = 0;
+	unsigned int	row_inc;
+	int		sshift, dshift, xshift, skip;
+
+	/* Pre-Digic 6 cameras: 8 bit per element UYVYYY,
+	 * 6 bytes used to encode 4 pixels, need 12 bytes raw YUV data for jpeg encoding */
+	if (fb_type == LV_FB_YUV8) {
+		row_inc = buf_width*1.5;
+		sshift = 6;
+		dshift = (width/height > 2) ? 6 : 12;
+		xshift = 4;
+	/* Digic 6 cameras: 8 bit per element UYVY,
+	 * 4 bytes used to encode 2 pixels, need 6 bytes raw YUV data for jpeg encoding */
+	} else {
+		row_inc = buf_width*2;
+		sshift = 4;
+		dshift = 6;
+		xshift = 2;
+	}
+	/* Encode only 2 pixels from each UV pair either if it is a UYVY data
+	 * (for Digic 6 cameras) or if the width to height ratio provided
+	 * by camera is too large (typically width 720 for height 240), so that
+	 * the resulting image would be stretched too much in the horizontal
+	 * direction if all 4 Y values were used. */
+	skip  = (fb_type > LV_FB_YUV8) || (width/height > 2);
+
+	cinfo.err = jpeg_std_error (&jerr);
+	jpeg_create_compress (&cinfo);
+
+	cinfo.image_width = (width/height > 2) ? (width/2) & -1 : width & -1;
+	cinfo.image_height = height & -1;
+	cinfo.input_components = 3;
+	cinfo.in_color_space = JCS_YCbCr; // input color space
+
+	jpeg_mem_dest (&cinfo, &outbuf, &outlen);
+	jpeg_set_defaults (&cinfo);
+	cinfo.dct_method = JDCT_IFAST; // DCT method
+	jpeg_set_quality (&cinfo, 70, TRUE);
+
+	jpeg_start_compress (&cinfo, TRUE);
+
+	tmprowbuf = malloc (cinfo.image_width * 3);
+	row_ptr[0] = &tmprowbuf[0];
+
+	while (cinfo.next_scanline < cinfo.image_height) {
+		unsigned int x, i, j;
+		/* offset to the correct row */
+		unsigned int offset = cinfo.next_scanline * row_inc;
+
+		for (x = 0, i = 0, j = 0; x < width; i += sshift, j += dshift, x += xshift) {
+			int8_t u = (int8_t) p_yuv[offset + i + 0];
+			int8_t v = (int8_t) p_yuv[offset + i + 2];
+			if (fb_type == LV_FB_YUV8) {
+				u += 0x80;
+				v += 0x80;
+			}
+
+			tmprowbuf[j + 0] = p_yuv[offset + i + 1];
+			tmprowbuf[j + 1] = u;
+			tmprowbuf[j + 2] = v;
+			tmprowbuf[j + 3] = p_yuv[offset + i + 3];
+			tmprowbuf[j + 4] = u;
+			tmprowbuf[j + 5] = v;
+
+			if (!skip) {
+				tmprowbuf[j + 6] = p_yuv[offset + i + 4];
+				tmprowbuf[j + 7] = u;
+				tmprowbuf[j + 8] = v;
+				tmprowbuf[j + 9] = p_yuv[offset + i + 5];
+				tmprowbuf[j +10] = u;
+				tmprowbuf[j +11] = v;
+			}
+		}
+		jpeg_write_scanlines (&cinfo, row_ptr, 1);
+	}
+	jpeg_finish_compress (&cinfo);
+	jpeg_destroy_compress (&cinfo);
+
+	gp_file_append (file, (char*)outbuf, outlen);
+      	gp_file_set_mime_type (file, GP_MIME_JPEG);
+      	gp_file_set_name (file, "chdk_preview.jpg");
+
+	free (outbuf);
+	free (tmprowbuf);
+}
+
+#else
+static inline uint8_t clip_yuv (int v) {
+	if (v<0) return 0;
+	if (v>255) return 255;
+	return v;
+}
+
+static inline uint8_t yuv_to_r (uint8_t y, int8_t v) {
+	return clip_yuv (((y<<12) +          v*5743 + 2048)>>12);
+}
+
+static inline uint8_t yuv_to_g (uint8_t y, int8_t u, int8_t v) {
+	return clip_yuv (((y<<12) - u*1411 - v*2925 + 2048)>>12);
+}
+
+static inline uint8_t yuv_to_b (uint8_t y, int8_t u) {
+	return clip_yuv (((y<<12) + u*7258          + 2048)>>12);
+}
+
+static void yuv_live_to_ppm (unsigned char *p_yuv,
+			     int buf_width, int width, int height,
+			     int fb_type, CameraFile *file
+) {
+	const unsigned char  *p_row = p_yuv;
+	const unsigned char  *p;
+	unsigned int	      row, x;
+	unsigned int	      row_inc;
+	int		      pshift, xshift, skip;
+	char		      ppm_header[32];
+	uint8_t		      rgb[6];
+
+	/* Pre-Digic 6 cameras:
+	 * 8 bit per element UYVYYY, 6 bytes used to encode 4 rgb values */
+	if (fb_type == LV_FB_YUV8) {
+		row_inc = buf_width*1.5;
+		pshift = 6;
+		xshift = 4;
+	/* Digic 6 cameras:
+	 * 8 bit per element UYVY, 4 bytes used to encode 2 rgb values */
+	} else {
+		row_inc = buf_width*2;
+		pshift = 4;
+		xshift = 2;
+	}
+	/* Encode only 2 pixels from each UV pair either if it is a UYVY data
+	 * (for Digic 6 cameras) or if the width to height ratio provided
+	 * by camera is too large (typically width 720 for height 240), so that
+	 * the resulting image would be stretched too much in the horizontal
+	 * direction if all 4 Y values were used. */
+	skip  = (fb_type > LV_FB_YUV8) || (width/height > 2);
+
+	sprintf (ppm_header, "P6 %d %d 255\n", (width/height > 2) ? width/2 : width, height);
+	gp_file_append (file, ppm_header, strlen (ppm_header));
+
+	for (row=0; row<height; row++, p_row += row_inc) {
+		for (x=0, p=p_row; x<width; x+=xshift, p+=pshift) {
+			/* these are signed unlike the Y values */
+			int8_t u = (int8_t) p[0];
+			int8_t v = (int8_t) p[2];
+			/* See for example
+			 * https://chdk.setepontos.com/index.php?topic=12692.msg130137#msg130137 */
+			if (fb_type > LV_FB_YUV8) {
+				u -= 0x80;
+				v -= 0x80;
+			}
+			rgb[0] = yuv_to_r (p[1], v);
+			rgb[1] = yuv_to_g (p[1], u, v);
+			rgb[2] = yuv_to_b (p[1], u);
+
+			rgb[3] = yuv_to_r (p[3], v);
+			rgb[4] = yuv_to_g (p[3], u, v);
+			rgb[5] = yuv_to_b (p[3], u);
+			gp_file_append (file, (char*)rgb, 6);
+
+			if (!skip) {
+				rgb[0] = yuv_to_r (p[4], v);
+				rgb[1] = yuv_to_g (p[4], u, v);
+				rgb[2] = yuv_to_b (p[4], u);
+
+				rgb[3] = yuv_to_r (p[5], v);
+				rgb[4] = yuv_to_g (p[5], u, v);
+				rgb[5] = yuv_to_b (p[5], u);
+				gp_file_append (file, (char*)rgb, 6);
+			}
+		}
+	}
+      	gp_file_set_mime_type (file, GP_MIME_PPM);
+      	gp_file_set_name (file, "chdk_preview.ppm");
+}
+#endif
+
+static int
+chdk_camera_capture_preview (Camera *camera, CameraFile *file, GPContext *context)
+{
+	unsigned char	*data = NULL;
+	uint32_t	size = 0;
+	PTPParams	*params = &camera->pl->params;
+	unsigned int	flags = LV_TFR_VIEWPORT;
+
+	lv_data_header header;
+	lv_framebuffer_desc vpd;
+	lv_framebuffer_desc bmd;
+
+	memset (&header, 0, sizeof (header));
+	memset (&vpd, 0, sizeof (vpd));
+	memset (&vpd, 0, sizeof (bmd));
+
+	CR (camera_prepare_chdk_capture (camera, context));
+      	C_PTP_REP_MSG (ptp_chdk_get_live_data (params, flags, &data, &size),
+      		       _("CHDK get live data failed"));
+	if (ptp_chdk_parse_live_data (params, data, size, &header, &vpd, &bmd) != PTP_RC_OK) {
+		gp_context_error (context, _("CHDK get live data failed: incomplete data (%d bytes) returned"), size);
+		return GP_ERROR;
+	}
+#ifdef HAVE_LIBJPEG
+	yuv_live_to_jpeg(data+vpd.data_start, vpd.buffer_width, vpd.visible_width,
+			 vpd.visible_height, vpd.fb_type, file);
+#else
+	yuv_live_to_ppm (data+vpd.data_start, vpd.buffer_width, vpd.visible_width,
+			 vpd.visible_height, vpd.fb_type, file);
+#endif
+
+      	free (data);
+      	gp_file_set_mtime (file, time (NULL));
+      	return GP_OK;
+}
+
 int
 chdk_init(Camera *camera, GPContext *context) {
         camera->functions->about = chdk_camera_about;
@@ -1160,9 +1386,9 @@
         camera->functions->summary = chdk_camera_summary;
         camera->functions->get_config = chdk_camera_get_config;
         camera->functions->set_config = chdk_camera_set_config;
+        camera->functions->capture_preview = chdk_camera_capture_preview;
 /*
         camera->functions->trigger_capture = camera_trigger_capture;
-        camera->functions->capture_preview = camera_capture_preview;
         camera->functions->wait_for_event = camera_wait_for_event;
 */
 
diff -ubE ptp2.orig/chdk_live_view.h ptp2/chdk_live_view.h
--- ptp2.orig/chdk_live_view.h	2018-03-22 13:54:17.509009162 +0300
+++ ptp2/chdk_live_view.h	2018-03-21 07:54:09.000000000 +0300
@@ -16,16 +16,19 @@
 - In some cases, the requested data may not be available. If this happens, the framebuffer
   or palette data offset will be zero. 
 - The frame buffer descriptions are returned regardless of whether the data is available
+- New enum values (e.g. aspect ratio, framebuffer type, palette type) may be added in minor
+  versions.
 */
 // Live View protocol version
 #define LIVE_VIEW_VERSION_MAJOR 2  // increase only with backwards incompatible changes (and reset minor)
-#define LIVE_VIEW_VERSION_MINOR 1  // increase with extensions of functionality
+#define LIVE_VIEW_VERSION_MINOR 2  // increase with extensions of functionality
 
 /*
 protocol version history
 < 2.0 - development versions
 2.0 - initial release, chdk 1.1
 2.1 - added palette type 4 - 16 entry VUYA, 2 bit alpha
+2.2 - in development digic 6 support. Added LV_ASPECT_3_2, LV_FB_YUV8B and LV_FB_YUV8C formats
 */
 
 
@@ -33,10 +36,13 @@
 #define LV_TFR_VIEWPORT     0x01
 #define LV_TFR_BITMAP       0x04
 #define LV_TFR_PALETTE      0x08
+#define LV_TFR_BITMAP_OPACITY   0x10
 
 enum lv_aspect_rato {
     LV_ASPECT_4_3,
     LV_ASPECT_16_9,
+    // below added in 2.2
+    LV_ASPECT_3_2,
 };
 
 /*
@@ -46,6 +52,10 @@
 enum lv_fb_type {
     LV_FB_YUV8, // 8 bit per element UYVYYY, used for live view
     LV_FB_PAL8, // 8 bit paletted, used for bitmap overlay. Note palette data and type sent separately
+    // below added in 2.2
+    LV_FB_YUV8B,// 8 bit per element UYVY, used for live view and overlay on Digic 6
+    LV_FB_YUV8C,// 8 bit per element UYVY, used for alternate Digic 6 live view
+    LV_FB_OPACITY8,// 8 bit opacity / alpha buffer
 };
 
 /*
@@ -94,6 +104,7 @@
     // framebuffer descriptions are given as offsets, to allow expanding the structures in minor protocol changes
     int vp_desc_start;
     int bm_desc_start;
+    int bmo_desc_start; // added in protocol 2.2
 } lv_data_header;
 
 #endif // __LIVE_VIEW_H
diff -ubE ptp2.orig/Makefile-files ptp2/Makefile-files
--- ptp2.orig/Makefile-files	2018-03-22 13:54:17.505009150 +0300
+++ ptp2/Makefile-files	2018-03-22 22:55:08.686187011 +0300
@@ -17,4 +17,5 @@
 	ptp2/chdk.c
 ptp2_la_LDFLAGS = $(camlib_ldflags)
 ptp2_la_DEPENDENCIES = $(camlib_dependencies)
-ptp2_la_LIBADD = $(camlib_libadd) $(LTLIBICONV) $(LIBXML2_LIBS)
+ptp2_la_LIBADD = $(camlib_libadd) $(LTLIBICONV) $(LIBXML2_LIBS) @LIBJPEG@
+
diff -ubE ptp2.orig/ptp.c ptp2/ptp.c
--- ptp2.orig/ptp.c	2018-03-22 13:54:17.509009162 +0300
+++ ptp2/ptp.c	2018-03-22 14:08:16.107810074 +0300
@@ -4469,7 +4469,29 @@
 	return PTP_RC_OK;
 }
 
+uint16_t
+ptp_chdk_parse_live_data (PTPParams* params, unsigned char *data, unsigned int data_size,
+			  lv_data_header *header,
+			  lv_framebuffer_desc *vpd, lv_framebuffer_desc *bmd
+) {
+	int byte_w;
+
+	if (data_size < sizeof (*header))
+		return PTP_ERROR_IO;
+	ptp_unpack_chdk_lv_data_header (params, data, header);
+	if (data_size < (header->vp_desc_start + sizeof (*vpd)) || data_size < (header->bm_desc_start + sizeof (*bmd)))
+		return PTP_ERROR_IO;
+	ptp_unpack_chdk_lv_framebuffer_desc (params, data+header->vp_desc_start, vpd);
+	ptp_unpack_chdk_lv_framebuffer_desc (params, data+header->vp_desc_start, bmd);
 
+	/* The buffer_width field corresponds to the number of Y values in a row,
+	 * so the actual number of bytes would be either one and a half times
+	 * or (for Digic 6 cameras) twice so large */
+	byte_w = (vpd->fb_type == LV_FB_YUV8) ? vpd->buffer_width * 1.5 : vpd->buffer_width * 2;
+	if (data_size < (vpd->data_start + (byte_w * vpd->visible_height)))
+		return PTP_ERROR_IO;
+	return PTP_RC_OK;
+}
 
 
 /**
diff -ubE ptp2.orig/ptp.h ptp2/ptp.h
--- ptp2.orig/ptp.h	2018-03-22 13:54:17.509009162 +0300
+++ ptp2/ptp.h	2018-03-22 13:56:49.205474341 +0300
@@ -3487,6 +3487,8 @@
 uint16_t ptp_chdk_write_script_msg(PTPParams* params, char *data, unsigned size, int target_script_id, int *status);
 uint16_t ptp_chdk_read_script_msg(PTPParams* params, ptp_chdk_script_msg **msg);
 uint16_t ptp_chdk_get_live_data(PTPParams* params, unsigned flags, unsigned char **data, unsigned int *data_size);
+uint16_t ptp_chdk_parse_live_data (PTPParams* params, unsigned char *data, unsigned int data_size,
+				   lv_data_header *header, lv_framebuffer_desc *vpd, lv_framebuffer_desc *bmd);
 uint16_t ptp_chdk_call_function(PTPParams* params, int *args, int size, int *ret);
 
 /*uint16_t ptp_chdk_get_script_output(PTPParams* params, char **output ); */
diff -ubE ptp2.orig/ptp-pack.c ptp2/ptp-pack.c
--- ptp2.orig/ptp-pack.c	2018-03-22 13:54:17.509009162 +0300
+++ ptp2/ptp-pack.c	2018-03-22 13:57:40.005630477 +0300
@@ -2954,3 +2954,37 @@
 	free (xoifs);
 	return 0;
 }
+
+static inline void
+ptp_unpack_chdk_lv_data_header (PTPParams *params, unsigned char* data, lv_data_header *header)
+{
+	int off = 0;
+	if (data==NULL)
+		return;
+	header->version_major = dtoh32a(&data[off]);
+	header->version_minor = dtoh32a(&data[off+=4]);
+	header->lcd_aspect_ratio = dtoh32a(&data[off+=4]);
+	header->palette_type = dtoh32a(&data[off+=4]);
+	header->palette_data_start = dtoh32a(&data[off+=4]);
+	header->vp_desc_start = dtoh32a(&data[off+=4]);
+	header->bm_desc_start = dtoh32a(&data[off+=4]);
+	if (header->version_minor > 1)
+		header->bmo_desc_start = dtoh32a(&data[off+=4]);
+}
+
+static inline void
+ptp_unpack_chdk_lv_framebuffer_desc (PTPParams *params, unsigned char* data, lv_framebuffer_desc *fd)
+{
+	int off = 0;
+	if (data==NULL)
+		return;
+	fd->fb_type = dtoh32a(&data[off]);
+	fd->data_start = dtoh32a(&data[off+=4]);
+	fd->buffer_width = dtoh32a(&data[off+=4]);
+	fd->visible_width = dtoh32a(&data[off+=4]);
+	fd->visible_height = dtoh32a(&data[off+=4]);
+	fd->margin_left = dtoh32a(&data[off+=4]);
+	fd->margin_top = dtoh32a(&data[off+=4]);
+	fd->margin_right = dtoh32a(&data[off+=4]);
+	fd->margin_bot = dtoh32a(&data[off+=4]);
+}
------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot
_______________________________________________
Gphoto-devel mailing list
Gphoto-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/gphoto-devel

Reply via email to