Add support for QuickTake 100/150 (#936)

* Add support for QuickTake 100/150

- Summary
- Get/Set configuration
- Get preview
- Get PPM files
- Get raw files (QTK format)

Serial protocol reference: https://www.colino.net/wordpress/en/archives/2023/10/29/the-apple-quicktake-100-serial-communication-protocol/
QTKT / QTKN decoding algorithms from dcraw (GPL licensed).

* Fix theorical integer overflows

* Fix license after careful re-reading of dcraw's license

* Fix quality mode index

* Fix Quicktake 150 thumbnails

* Fix unused variables and document thumbnail formats
This commit is contained in:
Colin Leroy-Mira
2024-01-07 11:18:19 +01:00
committed by GitHub
parent 5858949c57
commit 9a07ec3adb
12 changed files with 1637 additions and 0 deletions

View File

@@ -84,6 +84,9 @@ scanner.
Hans de Goede <hdegoede@redhat.com> Hans de Goede <hdegoede@redhat.com>
ST2205 Picture Frame support (and usbdiskdirect port driver). ST2205 Picture Frame support (and usbdiskdirect port driver).
Colin Leroy-Mira <colin@colino.net>
Apple QuickTake 1x0 driver.
========================================================================= =========================================================================
git migration authors git migration authors

View File

@@ -132,6 +132,7 @@ include pccam600/Makefile-files
include pentax/Makefile-files include pentax/Makefile-files
include polaroid/Makefile-files include polaroid/Makefile-files
include ptp2/Makefile-files include ptp2/Makefile-files
include quicktake1x0/Makefile-files
include ricoh/Makefile-files include ricoh/Makefile-files
include samsung/Makefile-files include samsung/Makefile-files
include sierra/Makefile-files include sierra/Makefile-files

View File

@@ -0,0 +1,20 @@
# -*- Makefile-automake -*-
#EXTRA_DIST += %reldir%/ChangeLog
EXTRA_LTLIBRARIES += quicktake1x0.la
quicktake1x0_la_SOURCES =
quicktake1x0_la_SOURCES += %reldir%/quicktake1x0.c
quicktake1x0_la_SOURCES += %reldir%/qtk-thumbnail-decoder.c
quicktake1x0_la_SOURCES += %reldir%/qtkt-decoder.c
quicktake1x0_la_SOURCES += %reldir%/qtkn-decoder.c
quicktake1x0_la_SOURCES += %reldir%/qtk-helpers.c
quicktake1x0_la_SOURCES += %reldir%/quicktake1x0.h
quicktake1x0_la_CFLAGS = $(camlib_cflags)
quicktake1x0_la_CPPFLAGS = $(camlib_cppflags)
quicktake1x0_la_DEPENDENCIES = $(camlib_dependencies)
quicktake1x0_la_LDFLAGS = $(camlib_ldflags)
quicktake1x0_la_LIBADD = $(camlib_libadd)

View File

@@ -0,0 +1,106 @@
/* qtk-helpers.c
*
Copyright 1997-2018 by Dave Coffin, dcoffin a cybercom o net
* Copyright 2023, Colin Leroy-Mira <colin@colino.net>
*
* getbithuff() heavily inspired from dcraw.c.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA
*/
#include "config.h"
#include "quicktake1x0.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <gphoto2/gphoto2-library.h>
#include <libgphoto2/bayer.h>
/* Write a basic .qtk header. This is imperfect and may not allow to open the
* raw files we generate with the official, vintage Quicktake software, but it
* is enough for dcraw to open and convert it.
*/
void
qtk_raw_header(unsigned char *data, const char *pic_format)
{
char hdr[] = {0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x04,0x00,0x00,0x73,0xE4,0x00,0x01};
memcpy(hdr, pic_format, 4);
memcpy(data, hdr, sizeof hdr);
}
char *qtk_ppm_header(int width, int height) {
char *header = malloc(128);
if (header == NULL)
return NULL;
snprintf(header, 127,
"P6\n%d %d\n%d\n",
width, height, 255);
return header;
}
int qtk_ppm_size(int width, int height) {
char *header;
int len;
header = qtk_ppm_header(width, height);
if (header == NULL) {
return GP_ERROR_NO_MEMORY;
}
len = (width * height * 3) + strlen(header);
free(header);
return len;
}
unsigned char getbithuff (int nbits, unsigned char **raw, ushort *huff)
{
static unsigned bitbuf = 0;
static int vbits = 0;
unsigned char c;
int h;
unsigned char *ptr;
if (nbits == -1) {
bitbuf = 0;
vbits = 0;
return 0;
}
ptr = *raw;
if (vbits < nbits) {
c = *ptr;
ptr++; (*raw)++;
bitbuf = (bitbuf << 8) + c;
vbits += 8;
}
c = bitbuf << (32-vbits) >> (32-nbits);
if (!huff)
vbits -= nbits;
else {
h = huff[c];
vbits -= h >> 8;
c = (unsigned char) h;
}
return c;
}

View File

@@ -0,0 +1,162 @@
/* qtkt-decoder.c
*
* Copyright 1997-2018 by Dave Coffin, dcoffin a cybercom o net
* Copyright 2023, Colin Leroy-Mira <colin@colino.net>
*
* QTKT decoder heavily inspired from dcraw.c.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA
*/
#include "config.h"
#include "quicktake1x0.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <gphoto2/gphoto2-library.h>
static int qt100_thumbnail_decode(unsigned char *raw, unsigned char **out) {
int s, v, p1, p2, r, g, b, len;
char *header;
unsigned char *ptr;
header = qtk_ppm_header(QT1X0_THUMB_WIDTH, QT1X0_THUMB_HEIGHT);
len = qtk_ppm_size(QT1X0_THUMB_WIDTH, QT1X0_THUMB_HEIGHT);
*out = ptr = calloc(1, len);
if (ptr == NULL) {
free(header);
return GP_ERROR_NO_MEMORY;
}
strcpy((char *)ptr, header);
ptr += strlen(header);
free(header);
/* The 2400 bytes buffer represent 80x60 pixels at 4bpp.
* It is very logical to decode as each half-byte represents
* the next pixel.
*/
for (s = 0; s < QT1X0_THUMB_SIZE; s++) {
v = raw[s];
p1 = ((v >> 4) & 0b00001111) << 4;
p2 = ((v >> 0) & 0b00001111) << 4;
/* FIXME do color thumbnails */
r = g = b = p1;
*ptr++ = r;
*ptr++ = g;
*ptr++ = b;
r = g = b = p2;
*ptr++ = r;
*ptr++ = g;
*ptr++ = b;
}
return GP_OK;
}
static int qt150_thumbnail_decode(unsigned char *raw, unsigned char **out) {
int len;
char *header;
unsigned char *ptr;
unsigned char *cur_in;
unsigned char line[QT1X0_THUMB_WIDTH*2], *cur_out;
int i, a, b, c, d, y;
header = qtk_ppm_header(QT1X0_THUMB_WIDTH, QT1X0_THUMB_HEIGHT);
len = qtk_ppm_size(QT1X0_THUMB_WIDTH, QT1X0_THUMB_HEIGHT);
*out = ptr = calloc(1, len);
if (ptr == NULL) {
free(header);
return GP_ERROR_NO_MEMORY;
}
strcpy((char *)ptr, header);
ptr += strlen(header);
free(header);
/* The 2400 bytes buffer represent 80x60 pixels at 4bpp.
* It is harder to decode as the half-bytes do not
* represent one pixel after the other.
* Every 80 bytes represent 2 lines. The first 60 bytes
* (120 half-bytes) represent pixels at:
* 0,y ; 1,y ; 0,y+1 ; 2,y ; 3,y ; 2,y+1 ; etc
* The last 20 bytes (40 half-bytes) represent the pixels
* at 1,y+1 ; 3,y+1 ; 5,y+1 ; etc
*/
cur_in = raw;
for (y = 0; y < QT1X0_THUMB_HEIGHT; y+=2) {
cur_out = line;
for (i = 0; i < QT1X0_THUMB_WIDTH; i++) {
c = *cur_in++;
a = (((c>>4) & 0b00001111) << 4);
b = (((c) & 0b00001111) << 4);
*cur_out++ = a;
*cur_out++ = b;
}
cur_out = line;
for (i = 0; i < QT1X0_THUMB_WIDTH * 2; ) {
if (i < QT1X0_THUMB_WIDTH*3/2) {
a = *cur_out++;
b = *cur_out++;
c = *cur_out++;
*(ptr) = a;
*(ptr + 3) = b;
*(ptr + 3*QT1X0_THUMB_WIDTH) = c;
ptr++;
*(ptr) = a;
*(ptr + 3) = b;
*(ptr + 3*QT1X0_THUMB_WIDTH) = c;
ptr++;
*(ptr) = a;
*(ptr + 3) = b;
*(ptr + 3*QT1X0_THUMB_WIDTH) = c;
ptr++;
i+=3;
ptr+=3;
} else {
i++;
ptr += 3;
d = *cur_out++;
*(ptr++) = d;
*(ptr++) = d;
*(ptr++) = d;
}
}
}
return GP_OK;
}
int qtk_thumbnail_decode(unsigned char *raw, unsigned char **out, Quicktake1x0Model model) {
if (model == QUICKTAKE_MODEL_100) {
return qt100_thumbnail_decode(raw, out);
} else {
return qt150_thumbnail_decode(raw, out);
}
}

View File

@@ -0,0 +1,207 @@
/* qtkn-decoder.c
*
Copyright 1997-2018 by Dave Coffin, dcoffin a cybercom o net
* Copyright 2023, Colin Leroy-Mira <colin@colino.net>
*
* QTKN (RADC) decoder heavily inspired from dcraw.c.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA
*/
#include "config.h"
#include "quicktake1x0.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <gphoto2/gphoto2-library.h>
#include <libgphoto2/bayer.h>
#define radc_token(tree, ptr) ((signed char) getbithuff(8, ptr, huff[tree]))
#define PREDICTOR (c ? (buf[c][y-1][x] + buf[c][y][x+1]) / 2 \
: (buf[c][y-1][x+1] + 2*buf[c][y-1][x] + buf[c][y][x+1]) / 4)
int qtkn_decode(unsigned char *raw, int width, int height, unsigned char **out) {
static const char src[] = {
1,1, 2,3, 3,4, 4,2, 5,7, 6,5, 7,6, 7,8,
1,0, 2,1, 3,3, 4,4, 5,2, 6,7, 7,6, 8,5, 8,8,
2,1, 2,3, 3,0, 3,2, 3,4, 4,6, 5,5, 6,7, 6,8,
2,0, 2,1, 2,3, 3,2, 4,4, 5,6, 6,7, 7,5, 7,8,
2,1, 2,4, 3,0, 3,2, 3,3, 4,7, 5,5, 6,6, 6,8,
2,3, 3,1, 3,2, 3,4, 3,5, 3,6, 4,7, 5,0, 5,8,
2,3, 2,6, 3,0, 3,1, 4,4, 4,5, 4,7, 5,2, 5,8,
2,4, 2,7, 3,3, 3,6, 4,1, 4,2, 4,5, 5,0, 5,8,
2,6, 3,1, 3,3, 3,5, 3,7, 3,8, 4,0, 5,2, 5,4,
2,0, 2,1, 3,2, 3,3, 4,4, 4,5, 5,6, 5,7, 4,8,
1,0, 2,2, 2,-2,
1,-3, 1,3,
2,-17, 2,-5, 2,5, 2,17,
2,-7, 2,2, 2,9, 2,18,
2,-18, 2,-9, 2,-2, 2,7,
2,-28, 2,28, 3,-49, 3,-9, 3,9, 4,49, 5,-79, 5,79,
2,-1, 2,13, 2,26, 3,39, 4,-16, 5,55, 6,-37, 6,76,
2,-26, 2,-13, 2,1, 3,-39, 4,16, 5,-55, 6,-76, 6,37
};
static const unsigned short pt[] =
{ 0,0, 1280,1344, 2320,3616, 3328,8000, 4095,16383, 65535,16383 };
unsigned short curve[0x10000];
unsigned short huff[19][256];
int row, col, tree, nreps, rep, step, i, c, s, r, x, y, val, len;
short last[3] = { 16,16,16 }, mul[3], buf[3][3][386];
char *header;
unsigned short *tmp;
unsigned char *tmp_c, *ptr;
header = qtk_ppm_header(width, height);
if (header == NULL)
return GP_ERROR_NO_MEMORY;
len = qtk_ppm_size(width, height);
tmp = malloc((size_t)width * (size_t)height * sizeof(unsigned short));
if (tmp == NULL) {
free(header);
return GP_ERROR_NO_MEMORY;
}
tmp_c = malloc((size_t)width * (size_t)height);
if (tmp_c == NULL) {
free(header);
free(tmp);
return GP_ERROR_NO_MEMORY;
}
for (i=2; i < 12; i+=2) {
for (c=pt[i-2]; c <= pt[i]; c++) {
curve[c] = (float) (c-pt[i-2]) / (pt[i]-pt[i-2]) * (pt[i+1]-pt[i-1]) + pt[i-1] + 0.5;
}
}
for (s=i=0; i < (int)sizeof src; i+=2) {
for (c=0; c < 256 >> src[i]; c++) {
((unsigned short *)huff)[s++] = src[i] << 8 | (unsigned char) src[i+1];
}
}
s = 3;
for (c=0; c < 256; c++) {
huff[18][c] = (8-s) << 8 | c >> s << s | 1 << (s-1);
}
getbits(-1, &raw);
for (i=0; i < (int)(sizeof(buf)/sizeof(short)); i++) {
((short *)buf)[i] = 2048;
}
for (row=0; row < height; row+=4) {
for (c=0; c < 3; c++) {
mul[c] = getbits(6, &raw);
}
for (c=0; c < 3; c++) {
val = ((0x1000000/last[c] + 0x7ff) >> 12) * mul[c];
s = val > 65564 ? 10:12;
x = ~(-1 << (s-1));
val <<= 12-s;
for (i=0; i < (int)(sizeof(buf[0])/sizeof(short)); i++) {
((short *)buf[c])[i] = (((short *)buf[c])[i] * val + x) >> s;
}
last[c] = mul[c];
for (r=0; r <= !c; r++) {
buf[c][1][width/2] = buf[c][2][width/2] = mul[c] << 7;
for (tree=1, col=width/2; col > 0; ) {
if ((tree = radc_token(tree, &raw))) {
col -= 2;
if (tree == 8) {
for (y=1; y < 3; y++) {
for (x=col+1; x >= col; x--) {
buf[c][y][x] = (unsigned char) radc_token(18, &raw) * mul[c];
}
}
} else {
for (y=1; y < 3; y++) {
for (x=col+1; x >= col; x--) {
buf[c][y][x] = radc_token(tree+10, &raw) * 16 + PREDICTOR;
}
}
}
} else
do {
nreps = (col > 2) ? radc_token(9, &raw) + 1 : 1;
for (rep=0; rep < 8 && rep < nreps && col > 0; rep++) {
col -= 2;
for (y=1; y < 3; y++) {
for (x=col+1; x >= col; x--) {
buf[c][y][x] = PREDICTOR;
}
}
if (rep & 1) {
step = radc_token(10, &raw) << 4;
for (y=1; y < 3; y++) {
for (x=col+1; x >= col; x--) {
buf[c][y][x] += step;
}
}
}
}
} while (nreps == 9);
}
for (y=0; y < 2; y++) {
for (x=0; x < width/2; x++) {
val = (buf[c][y+1][x] << 4) / mul[c];
if (val < 0) val = 0;
if (c) RAW(tmp, row+y*2+c-1,x*2+2-c) = val;
else RAW(tmp, row+r*2+y,x*2+y) = val;
}
}
memcpy (buf[c][0]+!c, buf[c][2], sizeof buf[c][0]-2*!c);
}
}
for (y=row; y < row+4; y++) {
for (x=0; x < width; x++) {
if ((x+y) & 1) {
r = x ? x-1 : x+1;
s = x+1 < width ? x+1 : x-1;
val = (RAW(tmp, y, x)-2048) + (RAW(tmp,y,r)+RAW(tmp,y,s))/2;
if (val < 0) val = 0;
RAW(tmp,y,x) = val;
}
}
}
}
for (i=0; i < height*width; i++) {
tmp_c[i] = LIM(curve[tmp[i]] >> 4, 0, 255);
}
*out = calloc(1, len);
if (*out == NULL) {
free(header);
free(tmp);
free(tmp_c);
return GP_ERROR_NO_MEMORY;
}
strcpy((char *)*out, header);
ptr = *out + strlen(header);
free(header);
gp_bayer_decode(tmp_c, width, height, ptr, BAYER_TILE_GBRG);
free(tmp);
free(tmp_c);
return GP_OK;
}
#undef PREDICTOR

View File

@@ -0,0 +1,122 @@
/* qtkt-decoder.c
*
Copyright 1997-2018 by Dave Coffin, dcoffin a cybercom o net
* Copyright 2023, Colin Leroy-Mira <colin@colino.net>
*
* QTKT decoder heavily inspired from dcraw.c.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA
*/
#include "config.h"
#include "quicktake1x0.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <gphoto2/gphoto2-library.h>
#include <libgphoto2/bayer.h>
int qtkt_decode(unsigned char *raw, int width, int height, unsigned char **out) {
char *header;
unsigned char *tmp, *ptr;
unsigned char pixel[484][644];
static const short gstep[16] =
{ -89,-60,-44,-32,-22,-15,-8,-2,2,8,15,22,32,44,60,89 };
static const short rstep[6][4] =
{ { -3,-1,1,3 }, { -5,-1,1,5 }, { -8,-2,2,8 },
{ -13,-3,3,13 }, { -19,-4,4,19 }, { -28,-6,6,28 } };
int rb, row, col, sharp, val=0, len;
header = qtk_ppm_header(width, height);
if (header == NULL)
return GP_ERROR_NO_MEMORY;
len = qtk_ppm_size(width, height);
getbits(-1, 0);
memset (pixel, 0x80, sizeof pixel);
for (row=2; row < height+2; row++) {
for (col=2+(row & 1); col < width+2; col+=2) {
val = ((pixel[row-1][col-1] + 2*pixel[row-1][col+1] +
pixel[row][col-2]) >> 2) + gstep[getbits(4, &raw)];
pixel[row][col] = val = LIM(val,0,255);
if (col < 4)
pixel[row][col-2] = pixel[row+1][~row & 1] = val;
if (row == 2)
pixel[row-1][col+1] = pixel[row-1][col+3] = val;
}
pixel[row][col] = val;
}
for (rb=0; rb < 2; rb++) {
for (row=2+rb; row < height+2; row+=2) {
for (col=3-(row & 1); col < width+2; col+=2) {
if (row < 4 || col < 4) {
sharp = 2;
} else {
val = ABS(pixel[row-2][col] - pixel[row][col-2])
+ ABS(pixel[row-2][col] - pixel[row-2][col-2])
+ ABS(pixel[row][col-2] - pixel[row-2][col-2]);
sharp = val < 4 ? 0 : val < 8 ? 1 : val < 16 ? 2 :
val < 32 ? 3 : val < 48 ? 4 : 5;
}
val = ((pixel[row-2][col] + pixel[row][col-2]) >> 1)
+ rstep[sharp][getbits(2, &raw)];
pixel[row][col] = val = LIM(val,0,255);
if (row < 4) pixel[row-2][col+2] = val;
if (col < 4) pixel[row+2][col-2] = val;
}
}
}
for (row=2; row < height+2; row++) {
for (col=3-(row & 1); col < width+2; col+=2) {
val = ((pixel[row][col-1] + (pixel[row][col] << 2) +
pixel[row][col+1]) >> 1) - 0x100;
pixel[row][col] = LIM(val,0,255);
}
}
tmp = malloc((size_t)(width + 4) * (size_t)(height + 4));
if (tmp == NULL) {
free(header);
return GP_ERROR_NO_MEMORY;
}
for (row=0; row < height; row++) {
for (col=0; col < width; col++) {
RAW(tmp, row, col) = pixel[row+2][col+2];
}
}
*out = calloc(1, len);
if (*out == NULL) {
free(header);
free(tmp);
return GP_ERROR_NO_MEMORY;
}
strcpy((char *)*out, header);
ptr = *out + strlen(header);
free(header);
gp_bayer_decode(tmp, width, height, ptr, BAYER_TILE_GBRG);
free(tmp);
return GP_OK;
}

View File

@@ -0,0 +1,927 @@
/* quicktake1x0.c
*
* Copyright 2023, Colin Leroy-Mira <colin@colino.net>
* Quicktake 100/150 protocol documentation available at
* https://www.colino.net/wordpress/en/archives/2023/10/29/the-apple-quicktake-100-serial-communication-protocol/
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA
*/
#include "config.h"
#include "libgphoto2/i18n.h"
#include "quicktake1x0.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <gphoto2/gphoto2-library.h>
#define GP_MODULE "Quicktake 1x0"
#define BUFFER_SIZE 1024
static unsigned char buffer[BUFFER_SIZE];
#define CHECK_RESULT(result) {int r = result; if (r < 0) return (r);}
/* Get an ack from the camera
* Acks are single-byte, 0x00 in case of success, 0x02 in case of error
*/
static int
get_ack(GPPort *port)
{
if (gp_port_read(port, (char *)buffer, 1) < 1)
return GP_ERROR_IO;
return buffer[0] == 0x00 ? GP_OK : GP_ERROR_IO;
}
/* Send an ack to the camera
* Our acks are single byte, 0x06 that mean "I'm ready to get data"
*/
static int
send_ack(GPPort *port)
{
char cmd[] = {0x06};
return gp_port_write (port, cmd, 1) == 1 ? GP_OK : GP_ERROR_IO;
}
/* Send a command to the camera
* cmd: the command as an hexadecimal char array
* len: the length of the array
* s_ack: whether the camera expects us to send an ack after we receive
* its own ack
*/
static int
send_command(GPPort *port, const char *cmd, int len, int s_ack)
{
if (gp_port_write (port, cmd, len) < GP_OK)
return GP_ERROR_IO;
if (get_ack(port) != GP_OK)
return GP_ERROR_IO;
if (s_ack)
return send_ack(port);
return GP_OK;
}
/* Ping the camera
* A simple command that does nothing, and that the camera replies to
* with an ack. Useful to make sure the camera did not go to sleep.
*/
static int
qt1x0_send_ping(GPPort *port)
{
char cmd[] = {0x16,0x00,0x00,0x00,0x00,0x00,0x00};
return send_command(port, cmd, sizeof cmd, 0);
}
/* Initialize the camera */
static int
qt1x0_initialize (Camera *camera)
{
/* "Hello" command, sent at 9600bps, 8n1. At bytes 6-7, the desired serial
* speed is indicated. the last byte is a checksum:
* (sum of the preceding bytes)&0xFF
*/
char hello[] = {0x5A,0xA5,0x55,0x05,0x00,0x00,0xE1,0x00,0x00,0x80,0x02,0x00,0xBC};
/* Command that triggers the serial speed upgrade */
char upgrade_speed[] = {0x16,0x2A,0x00,0x03,0x00,0x00,0x00,0x00,0x00,0x05,0x00,0x03,0x03,0x30,0x04,0x00};
GPPortSettings settings;
/* Clearing DTR wakes the camera up. */
CHECK_RESULT(gp_port_set_pin (camera->port, GP_PIN_DTR, GP_LEVEL_LOW));
usleep(100 * 1000);
CHECK_RESULT(gp_port_set_pin (camera->port, GP_PIN_DTR, GP_LEVEL_HIGH));
/* It then tells us 7 bytes that help identify it */
if (gp_port_read (camera->port, (char *)buffer, 7) < 7)
return GP_ERROR_MODEL_NOT_FOUND;
if ((unsigned char)buffer[0] != 0xA5)
return GP_ERROR_MODEL_NOT_FOUND;
if ((unsigned char)buffer[3] == 0xC8)
camera->pl->model = QUICKTAKE_MODEL_150;
else
camera->pl->model = QUICKTAKE_MODEL_100;
/* We then send our first command to inform it of the speed we desire */
if (gp_port_write (camera->port, hello, sizeof(hello)) < GP_OK)
return GP_ERROR_MODEL_NOT_FOUND;
/* The camera replies, with data we can ignore */
if (gp_port_read (camera->port, (char *)buffer, 10) < 10)
return GP_ERROR_MODEL_NOT_FOUND;
/* At this point, the camera expects us to set even parity. */
CHECK_RESULT (gp_port_get_settings (camera->port, &settings));
settings.serial.parity = GP_PORT_SERIAL_PARITY_EVEN;
CHECK_RESULT (gp_port_set_settings (camera->port, settings));
/* We let it do the same. */
usleep(1 * 1000 * 1000);
/* Once parity is set on both sides, we ask to upgrade speed. */
if (gp_port_write (camera->port, upgrade_speed, sizeof(upgrade_speed)) < GP_OK)
return GP_ERROR_MODEL_NOT_FOUND;
/* The camera acks that, */
if (get_ack(camera->port) != GP_OK)
return GP_ERROR_MODEL_NOT_FOUND;
/* We ack the ack, */
if (send_ack(camera->port) != GP_OK)
return GP_ERROR_MODEL_NOT_FOUND;
/* Wait a little bit of time, */
usleep(100 * 1000);
/* Upgrade our serial line, */
settings.serial.speed = 57600;
CHECK_RESULT (gp_port_set_settings (camera->port, settings));
/* And flush a full kilobytes of 0xAA that the camera sends us,
* probably to verify that we're good to go.
*/
gp_port_read (camera->port, (char *)buffer, 1024); /* Ignored */
/* We ack that, */
if (send_ack(camera->port) != GP_OK)
return GP_ERROR_MODEL_NOT_FOUND;
/* It acks our ack, and we're ready! */
return get_ack(camera->port);
}
/* Get information from the camera. It tells us how many pics were taken,
* how much free space there is, its current battery level, quality mode,
* flash mode, and date and time.
*/
static int
camera_get_info (Camera *camera)
{
/* Command to get information from the camera.
* byte 3 is the requested information:
* - 0x00 for a thumbnail
* - 0x10 for a full picture
* - 0x21 for metadata
* - 0x30 for camera information
* byte 6 is the picture number. (0 for camera information)
* bytes 7 to 9 is the expected response size:
* - 0x000040 for metadata
* - 0x000080 for camera information
* - 0x000960 for the thumbnail
* - depends on the picture for the full picture. We get the full
* picture size in the metadata response so we can reuse that.
*/
char cmd[] = {0x16,0x28,0x00,0x30,0x00,0x00,0x00,0x00,0x00,0x80,0x00};
/* Offsets of interesting data */
#define BATTERY_IDX 0x02
#define NUM_PICS_IDX 0x04
#define LEFT_PICS_IDX 0x06
#define MONTH_IDX 0x10
#define DAY_IDX 0x11
#define YEAR_IDX 0x12
#define HOUR_IDX 0x13
#define MIN_IDX 0x14
#define SEC_IDX 0x15
#define FLASH_IDX 0x16
#define QUALITY_IDX 0x1B
#define NAME_IDX 0x2F
/* Ping it */
if (qt1x0_send_ping(camera->port) != GP_OK)
return GP_ERROR_IO;
/* Send our command */
if (send_command(camera->port, cmd, sizeof cmd, 1) != GP_OK)
return GP_ERROR_IO;
/* Read the 128 bytes response */
if (gp_port_read(camera->port, (char *)buffer, 128) != 128)
return GP_ERROR_IO;
/* Copy the relevant data. */
strcpy(camera->pl->name, (char *)buffer + NAME_IDX);
while (camera->pl->name[strlen(camera->pl->name) - 1] == ' ')
camera->pl->name[strlen(camera->pl->name) - 1] = '\0';
camera->pl->num_pics = buffer[NUM_PICS_IDX];
camera->pl->free_pics = buffer[LEFT_PICS_IDX];
camera->pl->battery_level = buffer[BATTERY_IDX];
camera->pl->quality_mode = buffer[QUALITY_IDX];
camera->pl->flash_mode = buffer[FLASH_IDX];
camera->pl->year = buffer[YEAR_IDX] + 2000;
camera->pl->month = buffer[MONTH_IDX];
camera->pl->day = buffer[DAY_IDX];
camera->pl->hour = buffer[HOUR_IDX];
camera->pl->minute = buffer[MIN_IDX];
camera->pl->info_fetched = 1;
return GP_OK;
}
/* Change the name of the camera */
static int
camera_set_name(Camera *camera, const char *name)
{
char set_name_cmd[] = {0x16,0x2a,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x22,0x00,0x02,0x20,
0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20};
#define SET_NAME_IDX 0x0D
int len;
len = strlen(name);
if (len > 31)
len = 31;
if (qt1x0_send_ping(camera->port) != GP_OK)
return GP_ERROR_IO;
memcpy(set_name_cmd + SET_NAME_IDX, name, len);
return send_command(camera->port, set_name_cmd, sizeof set_name_cmd, 0);
}
/* Change the date of the camera */
static int
camera_set_time(Camera *camera, int year, int month, int day, int hour, int minute)
{
char set_time_cmd[] = {0x16,0x2A,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x08,0x00,0x01,0x06,0x00,0x00,0x00,0x00,0x00,0x00};
#define SET_MONTH_IDX 0x0D
#define SET_DAY_IDX 0x0E
#define SET_YEAR_IDX 0x0F
#define SET_HOUR_IDX 0x10
#define SET_MIN_IDX 0x11
#define SET_SEC_IDX 0x12
if (qt1x0_send_ping(camera->port) != GP_OK)
return GP_ERROR_IO;
set_time_cmd[SET_DAY_IDX] = day;
set_time_cmd[SET_MONTH_IDX] = month;
set_time_cmd[SET_YEAR_IDX] = year % 100;
set_time_cmd[SET_HOUR_IDX] = hour;
set_time_cmd[SET_MIN_IDX] = minute;
set_time_cmd[SET_SEC_IDX] = 0;
return send_command(camera->port, set_time_cmd, sizeof set_time_cmd, 0);
}
/* Change the flash mode of the camera */
static int
camera_set_flash_mode(Camera *camera, Quicktake1x0FlashMode flash_mode)
{
char set_flash_cmd[] = {0x16,0x2A,0x00,0x07,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x07,0x01,0x00};
#define SET_FLASH_IDX 0x0D
set_flash_cmd[SET_FLASH_IDX] = flash_mode;
return send_command(camera->port, set_flash_cmd, sizeof set_flash_cmd, 0);
}
/* Change the quality_mode of the camera */
static int
camera_set_quality_mode(Camera *camera, QuickTake1x0Quality quality_mode)
{
char set_quality_cmd[] = {0x16,0x2A,0x00,0x06,0x00,0x00,0x00,0x00,0x00,0x04,0x00,0x06,0x02,0x10,0x00};
#define SET_QUALITY_IDX 0x0D
set_quality_cmd[SET_QUALITY_IDX] = quality_mode == QUALITY_HIGH ? 0x10 : 0x20;
return send_command(camera->port, set_quality_cmd, sizeof set_quality_cmd, 0);
}
static time_t
camera_get_time(Camera *camera)
{
struct tm tm_time;
/* Build a UNIX representation of the picture creation time */
tm_time.tm_year = camera->pl->year - 1900;
tm_time.tm_mon = camera->pl->month - 1;
tm_time.tm_mday = camera->pl->day;
tm_time.tm_hour = camera->pl->hour;
tm_time.tm_min = camera->pl->minute;
tm_time.tm_sec = 0;
tm_time.tm_isdst = -1;
return mktime(&tm_time);
}
static int
camera_trigger_capture (Camera *camera, GPContext *context)
{
char cmd[] = {0x16,0x1B,0x00,0x00,0x00,0x00,0x00};
/* Ping, */
if (qt1x0_send_ping(camera->port) != GP_OK)
return GP_ERROR_IO;
/* Command, */
if (send_command(camera->port, cmd, sizeof cmd, 0) != GP_OK)
return GP_ERROR_IO;
/* And update our information to keep track of the
* taken/free pics counters
*/
return camera_get_info(camera);
}
/* Command the camera to capture an image. */
static int
camera_capture (Camera *camera, CameraCaptureType type, CameraFilePath *path,
GPContext *context)
{
if (type != GP_CAPTURE_IMAGE)
return (GP_ERROR_NOT_SUPPORTED);
if (camera_trigger_capture(camera, context) != GP_OK)
return GP_ERROR_IO;
strcpy (path->folder, "/");
sprintf (path->name, "Image_%02i.ppm", camera->pl->num_pics);
CHECK_RESULT (gp_filesystem_append (camera->fs, "/", path->name, context));
return (GP_OK);
}
int
camera_abilities (CameraAbilitiesList *list)
{
CameraAbilities a;
memset (&a, 0, sizeof(a));
strcpy (a.model, "Apple QuickTake 1x0");
a.status = GP_DRIVER_STATUS_PRODUCTION;
a.port = GP_PORT_SERIAL;
a.speed[0] = 57600;
a.speed[1] = 0;
a.operations = GP_OPERATION_CAPTURE_IMAGE|GP_OPERATION_CONFIG;
a.file_operations = GP_FILE_OPERATION_RAW|GP_FILE_OPERATION_PREVIEW;
a.folder_operations = GP_FOLDER_OPERATION_DELETE_ALL;
CHECK_RESULT (gp_abilities_list_append (list, a));
return GP_OK;
}
static int
camera_about (Camera *camera, CameraText *about, GPContext *context)
{
/* Translators: please write 'M"uller' and 'Mei"sner' (that
is, with u-umlaut and eszett resp.) if your charset
allows it. If not, use "Mueller" and "Meissner". */
strcpy (about->text, _("The Apple QuickTake 1x0 driver has been written "
"by Colin Leroy-Mira <colin@colino.net>.\n"
"It handles the QuickTake 100, 100 plus and 150 models.\n"
"\n"
"It can fetch information, thumbnails, raw and ppm data, and "
"command the camera to take pictures.")
);
return GP_OK;
}
static const char *
model_to_str(int model)
{
switch(model) {
case QUICKTAKE_MODEL_100:
return "QuickTake 100";
case QUICKTAKE_MODEL_150:
return "QuickTake 150";
default:
return "unknown";
}
}
static const char *
quality_to_str(int mode)
{
switch(mode) {
case QUALITY_STANDARD:
return _("Standard");
case QUALITY_HIGH:
return _("High");
default:
return _("Unknown");
}
}
static const char *
flash_to_str(int mode)
{
switch(mode) {
case FLASH_AUTO:
return _("Automatic");
case FLASH_OFF:
return _("Disabled");
case FLASH_ON:
return _("Forced");
default:
return _("Unknown");
}
}
/* Show the information we gathered */
static int
camera_summary (Camera *camera, CameraText *summary, GPContext *context)
{
if (!camera->pl->info_fetched &&
camera_get_info(camera) != GP_OK)
return GP_ERROR_IO;
sprintf(summary->text,
"Camera model: %s\n"
"Camera name: %s\n"
"Pictures taken: %d\n"
"Available space: %d\n"
"\n"
"Battery level: %d%%\n"
"Current quality mode: %s\n"
"Current flash mode: %s\n"
"Camera date and time: %04d/%02d/%02d %02d:%02d\n",
model_to_str(camera->pl->model),
camera->pl->name,
camera->pl->num_pics,
camera->pl->free_pics,
camera->pl->battery_level,
quality_to_str(camera->pl->quality_mode),
flash_to_str(camera->pl->flash_mode),
camera->pl->year,
camera->pl->month,
camera->pl->day,
camera->pl->hour,
camera->pl->minute);
return GP_OK;
}
/* Get the list of files on the camera, that is, images 1 to N */
static int
file_list_func (CameraFilesystem *fs, const char *folder, CameraList *list,
void *data, GPContext *context)
{
Camera *camera = data;
if (!camera->pl->info_fetched &&
camera_get_info(camera) != GP_OK)
return GP_ERROR_IO;
return gp_list_populate (list, "Image_%02i.ppm", camera->pl->num_pics);
}
#define BLOCK_SIZE 512
#define WH_OFFSET 0x220
#define DATA_OFFSET 0x2E0
/* Camera "metadata" command. Elicits a 64-byte response containing
* metadata about the picture N: width, height, size, creation time,
* quality mode, flash mode.
*/
static int
get_metadata (GPPort *port, int n, unsigned char *data)
{
/* Command to get information from the camera (see camera_get_info
* for value details).
*/
char cmd[] = {0x16,0x28,0x00,0x21,0x00,0x00,0x01,0x00,0x00,0x40,0x00};
#define PNUM_IDX 0x06
#define PSIZE_IDX 0x07
#define IMG_NUM_IDX 0x03
#define IMG_SIZE_IDX 0x05
#define IMG_WIDTH_IDX 0x08
#define IMG_HEIGHT_IDX 0x0A
#define IMG_MONTH_IDX 0x0D
#define IMG_DAY_IDX 0x0E
#define IMG_YEAR_IDX 0x0F
#define IMG_HOUR_IDX 0x10
#define IMG_MINUTE_IDX 0x11
#define IMG_SECOND_IDX 0x12
#define IMG_FLASH_IDX 0x13
#define IMG_QUALITY_IDX 0x18
cmd[PNUM_IDX] = n;
if (qt1x0_send_ping(port) != GP_OK)
return GP_ERROR_IO;
if (send_command(port, cmd, sizeof cmd, 1) != GP_OK)
return GP_ERROR_IO;
if (gp_port_read(port, (char *)data, 64) != 64)
return GP_ERROR_IO;
return (GP_OK);
}
static int
width_from_metadata(const unsigned char *buffer)
{
return (buffer[IMG_WIDTH_IDX + 1]) + (buffer[IMG_WIDTH_IDX] << 8);
}
static int
height_from_metadata(const unsigned char *buffer)
{
return (buffer[IMG_HEIGHT_IDX + 1]) + (buffer[IMG_HEIGHT_IDX] << 8);
}
static int
size_from_metadata(const unsigned char *buffer)
{
return (buffer[IMG_SIZE_IDX] << 16)
+ (buffer[IMG_SIZE_IDX + 1] << 8)
+ (buffer[IMG_SIZE_IDX + 2]);
}
static time_t
mtime_from_metadata(const unsigned char *buffer)
{
struct tm tm_time;
/* Build a UNIX representation of the picture creation time */
tm_time.tm_year = buffer[IMG_YEAR_IDX] + 100;
tm_time.tm_mon = buffer[IMG_MONTH_IDX] - 1;
tm_time.tm_mday = buffer[IMG_DAY_IDX];
tm_time.tm_hour = buffer[IMG_HOUR_IDX];
tm_time.tm_min = buffer[IMG_MINUTE_IDX];
tm_time.tm_sec = buffer[IMG_SECOND_IDX];
tm_time.tm_isdst = 0;
return mktime(&tm_time);
}
static int
get_info_func (CameraFilesystem *fs, const char *folder, const char *file,
CameraFileInfo *info, void *data, GPContext *context)
{
Camera *camera = data;
int n;
CHECK_RESULT (n = gp_filesystem_number (fs, folder, file, context));
/* The camera counts from 1. */
n++;
if (get_metadata(camera->port, n, buffer) != GP_OK)
return GP_ERROR_IO;
/* Build the file information */
info->file.fields = GP_FILE_INFO_SIZE | GP_FILE_INFO_WIDTH |
GP_FILE_INFO_HEIGHT | GP_FILE_INFO_TYPE |
GP_FILE_INFO_MTIME;
info->file.width = width_from_metadata(buffer);
info->file.height = height_from_metadata(buffer);
info->file.mtime = mtime_from_metadata(buffer);
info->file.size = qtk_ppm_size(info->file.width, info->file.height);
strcpy (info->file.type, GP_MIME_PPM);
/* Build the preview information */
info->preview.fields = GP_FILE_INFO_SIZE | GP_FILE_INFO_WIDTH |
GP_FILE_INFO_HEIGHT | GP_FILE_INFO_TYPE;
info->preview.width = QT1X0_THUMB_WIDTH;
info->preview.height = QT1X0_THUMB_HEIGHT;
info->preview.size = qtk_ppm_size(QT1X0_THUMB_WIDTH, QT1X0_THUMB_HEIGHT);
strcpy (info->preview.type, GP_MIME_PPM);
return (GP_OK);
}
/* Read (long) data from the camera. Wraps around gp_port_read, as the
* camera expects us to tell it to continue every 512 bytes.
*/
static int
receive_data(GPPort *port, unsigned char *data, int size)
{
int i;
for (i = 0; i < (size / BLOCK_SIZE); i++) {
/* Read, */
if (gp_port_read(port, (char *)data, BLOCK_SIZE) != BLOCK_SIZE)
return GP_ERROR_IO;
data += BLOCK_SIZE;
/* ack, */
send_ack(port);
}
/* and read the last partial block */
size %= BLOCK_SIZE;
if (gp_port_read(port, (char *)data, size) != size)
return GP_ERROR_IO;
return GP_OK;
}
static int
get_file_func (CameraFilesystem *fs, const char *folder, const char *filename,
CameraFileType type, CameraFile *file, void *user_data,
GPContext *context)
{
/* Commands to get information from the camera (see camera_get_info
* for value details).
*/
char full_photo_cmd[] = {0x16,0x28,0x00,0x10,0x00,0x00,0x01,0x00,0x70,0x80,0x00};
char thumbnail_cmd[] = {0x16,0x28,0x00,0x00,0x00,0x00,0x01,0x00,0x09,0x60,0x00};
Camera *camera = user_data;
int n, raw_size, file_size, width = 0, height = 0;
unsigned char *data;
char *cmd;
const char *mime_type;
time_t mtime;
CHECK_RESULT (n = gp_filesystem_number (camera->fs, folder, filename, context));
/* The camera counts from 1 */
n++;
/* Get metadata */
if (get_metadata(camera->port, n, buffer) != GP_OK)
return GP_ERROR_IO;
mtime = mtime_from_metadata(buffer);
/* Set parameters according to the user's request */
if (type == GP_FILE_TYPE_PREVIEW) {
cmd = thumbnail_cmd;
mime_type = GP_MIME_PPM;
raw_size = QT1X0_THUMB_SIZE;
file_size = qtk_ppm_size(QT1X0_THUMB_WIDTH, QT1X0_THUMB_HEIGHT);
} else if (type == GP_FILE_TYPE_RAW) {
cmd = full_photo_cmd;
mime_type = camera->pl->model == QUICKTAKE_MODEL_100 ? GP_MIME_QTKT : GP_MIME_QTKN;
raw_size = size_from_metadata(buffer);
file_size = raw_size + DATA_OFFSET;
} else if (type == GP_FILE_TYPE_NORMAL) {
cmd = full_photo_cmd;
mime_type = GP_MIME_PPM;
width = width_from_metadata(buffer);
height = height_from_metadata(buffer);
raw_size = size_from_metadata(buffer);
file_size = qtk_ppm_size(width, height);
} else
return GP_ERROR_NOT_SUPPORTED;
/* Set the command's parameters */
cmd[PNUM_IDX] = n;
cmd[PSIZE_IDX] = (raw_size >> 16) & 0xFF;
cmd[PSIZE_IDX + 1] = (raw_size >> 8) & 0xFF;
cmd[PSIZE_IDX + 2] = (raw_size >> 0) & 0xFF;
data = malloc(file_size);
if (data == NULL)
return GP_ERROR_NO_MEMORY;
memset(data, 0, file_size);
/* Send the command to the camera */
if (send_command(camera->port, cmd, sizeof full_photo_cmd, 1) != GP_OK)
return GP_ERROR_IO;
if (type == GP_FILE_TYPE_PREVIEW) {
unsigned char *ppm_data;
/* Get the data */
CHECK_RESULT(receive_data(camera->port, data, raw_size));
/* And decode the thumbnail to PPM */
CHECK_RESULT(qtk_thumbnail_decode(data, &ppm_data, camera->pl->model));
free(data);
data = ppm_data;
} else if (type == GP_FILE_TYPE_RAW) {
/* Build the .qtk file header */
qtk_raw_header(data, camera->pl->model == QUICKTAKE_MODEL_100 ? "qktk":"qktn");
memcpy(data + 0x12, buffer + 4, 60);
data[WH_OFFSET] = buffer[IMG_HEIGHT_IDX];
data[WH_OFFSET + 1] = buffer[IMG_HEIGHT_IDX + 1];
data[WH_OFFSET + 2] = buffer[IMG_WIDTH_IDX];
data[WH_OFFSET + 3] = buffer[IMG_WIDTH_IDX + 1];
/* And get data after the header */
CHECK_RESULT(receive_data(camera->port, data + DATA_OFFSET, raw_size));
} else if (type == GP_FILE_TYPE_NORMAL) {
unsigned char *ppm_data;
/* Get the data */
CHECK_RESULT(receive_data(camera->port, data, raw_size));
/* And decode it to PPM */
if (camera->pl->model == QUICKTAKE_MODEL_100) {
CHECK_RESULT(qtkt_decode(data, width, height, &ppm_data));
} else {
CHECK_RESULT(qtkn_decode(data, width, height, &ppm_data));
}
free(data);
data = ppm_data;
} else
return GP_ERROR_NOT_SUPPORTED;
/* Finalize the file */
CHECK_RESULT (gp_file_set_mtime (file, mtime));
CHECK_RESULT (gp_file_set_mime_type (file, mime_type));
CHECK_RESULT (gp_file_set_data_and_size (file, (char*)data, file_size));
return GP_OK;
}
/* Command to delete all files from the camera */
static int
delete_all_func (CameraFilesystem *fs, const char* folder, void *data,
GPContext *context)
{
char cmd[] = {0x16,0x29,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
Camera *camera = data;
if (strcmp (folder, "/"))
return (GP_ERROR_DIRECTORY_NOT_FOUND);
if (qt1x0_send_ping(camera->port) != GP_OK) {
return GP_ERROR_IO;
}
return send_command(camera->port, cmd, sizeof cmd, 1);
}
static CameraFilesystemFuncs fsfuncs = {
.file_list_func = file_list_func,
.get_info_func = get_info_func,
.get_file_func = get_file_func,
.delete_all_func = delete_all_func,
};
static int
camera_config_get (Camera *camera, CameraWidget **window, GPContext *context)
{
CameraWidget *child;
time_t t;
if (!camera->pl->info_fetched &&
camera_get_info(camera) != GP_OK)
return GP_ERROR_IO;
gp_widget_new (GP_WIDGET_WINDOW, _("Camera Configuration"), window);
gp_widget_new (GP_WIDGET_TEXT, _("Camera name"), &child);
gp_widget_set_name (child, "camera_name");
gp_widget_set_value (child, camera->pl->name);
gp_widget_append (*window, child);
gp_widget_new (GP_WIDGET_RADIO, _("Image quality"), &child);
gp_widget_set_name (child, "quality_mode");
gp_widget_add_choice (child, quality_to_str(QUALITY_STANDARD));
gp_widget_add_choice (child, quality_to_str(QUALITY_HIGH));
gp_widget_set_value (child, quality_to_str(camera->pl->quality_mode));
gp_widget_append (*window, child);
gp_widget_new (GP_WIDGET_RADIO, _("Flash"), &child);
gp_widget_set_name (child, "flash_mode");
gp_widget_add_choice (child, flash_to_str(FLASH_ON));
gp_widget_add_choice (child, flash_to_str(FLASH_OFF));
gp_widget_add_choice (child, flash_to_str(FLASH_AUTO));
gp_widget_set_value (child, flash_to_str(camera->pl->flash_mode));
gp_widget_append (*window, child);
gp_widget_new (GP_WIDGET_DATE, _("Camera date"), &child);
gp_widget_set_name (child, "camera_date");
t = camera_get_time(camera);
gp_widget_set_value (child, &t);
gp_widget_append (*window, child);
return GP_OK;
}
static int
camera_config_set (Camera *camera, CameraWidget *window, GPContext *context)
{
CameraWidget *widget;
const char *val;
CHECK_RESULT(gp_widget_get_child_by_name (window, "camera_name", &widget));
if (gp_widget_changed(widget)) {
CHECK_RESULT(gp_widget_get_value(widget, &val));
CHECK_RESULT(camera_set_name(camera, val));
}
CHECK_RESULT(gp_widget_get_child_by_name (window, "quality_mode", &widget));
if (gp_widget_changed(widget)) {
CHECK_RESULT(gp_widget_get_value(widget, &val));
if (!strcmp(val, quality_to_str(QUALITY_HIGH))) {
CHECK_RESULT(camera_set_quality_mode(camera, QUALITY_HIGH));
} else {
CHECK_RESULT(camera_set_quality_mode(camera, QUALITY_STANDARD));
}
}
CHECK_RESULT(gp_widget_get_child_by_name (window, "flash_mode", &widget));
if (gp_widget_changed(widget)) {
CHECK_RESULT(gp_widget_get_value(widget, &val));
if (!strcmp(val, flash_to_str(FLASH_ON))) {
CHECK_RESULT(camera_set_flash_mode(camera, FLASH_ON));
} else if (!strcmp(val, flash_to_str(FLASH_OFF))) {
CHECK_RESULT(camera_set_flash_mode(camera, FLASH_OFF));
} else {
CHECK_RESULT(camera_set_flash_mode(camera, FLASH_AUTO));
}
}
CHECK_RESULT(gp_widget_get_child_by_name (window, "camera_date", &widget));
if (gp_widget_changed(widget)) {
int v;
time_t time;
struct tm *tm_time;
CHECK_RESULT(gp_widget_get_value(widget, &v));
time = (time_t)v;
tm_time = localtime(&time);
CHECK_RESULT(camera_set_time(camera,
tm_time->tm_year + 1900, tm_time->tm_mon + 1,
tm_time->tm_mday, tm_time->tm_hour, tm_time->tm_min));
}
return camera_get_info(camera);
}
static int
camera_exit (Camera *camera, GPContext *context)
{
if (camera->pl) {
free (camera->pl);
camera->pl = NULL;
}
return (GP_OK);
}
int
camera_id (CameraText *id)
{
strcpy (id->text, "Apple QuickTake 1x0");
return GP_OK;
}
int camera_init (Camera *camera, GPContext *context)
{
GPPortSettings settings;
GP_DEBUG("Init QuickTake 1x0...");
/* First, set up all the function pointers */
camera->functions->about = camera_about;
camera->functions->summary = camera_summary;
camera->functions->capture = camera_capture;
camera->functions->trigger_capture = camera_trigger_capture;
camera->functions->exit = camera_exit;
camera->functions->get_config = camera_config_get;
camera->functions->set_config = camera_config_set;
camera->pl = calloc (1, sizeof (CameraPrivateLibrary));
if (!camera->pl)
return (GP_ERROR_NO_MEMORY);
camera->pl->model = QUICKTAKE_MODEL_UNKNOWN;
/* Now, tell the filesystem where to get lists and info */
gp_filesystem_set_funcs (camera->fs, &fsfuncs, camera);
/* Initial serial settings */
CHECK_RESULT (gp_port_get_settings (camera->port, &settings));
settings.serial.speed = 9600;
settings.serial.bits = 8;
settings.serial.parity = GP_PORT_SERIAL_PARITY_OFF;
settings.serial.stopbits= 1;
CHECK_RESULT (gp_port_set_settings (camera->port, settings));
CHECK_RESULT (gp_port_set_timeout (camera->port, 20000));
/* Open the port and check if the camera is there */
CHECK_RESULT (qt1x0_initialize (camera));
return (GP_OK);
}

View File

@@ -0,0 +1,82 @@
/* quicktake1x0.h
*
* Copyright 2023 Colin Leroy-Mira <colin@colino.net>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA
*/
#ifndef CAMLIBS_QUICKTAKE_1X0_H
#define CAMLIBS_QUICKTAKE_1X0_H
#define CHECK_RESULT(result) {int r = result; if (r < 0) return (r);}
typedef enum {
QUICKTAKE_MODEL_UNKNOWN = 0,
QUICKTAKE_MODEL_100,
QUICKTAKE_MODEL_150,
} Quicktake1x0Model;
typedef enum {
QUALITY_HIGH = 0x10,
QUALITY_STANDARD = 0x20
} QuickTake1x0Quality;
typedef enum {
FLASH_AUTO = 0,
FLASH_OFF,
FLASH_ON
} Quicktake1x0FlashMode;
struct _CameraPrivateLibrary {
Quicktake1x0Model model;
int info_fetched;
Quicktake1x0FlashMode flash_mode;
QuickTake1x0Quality quality_mode;
int num_pictures;
char name[33];
int num_pics;
int free_pics;
int battery_level;
int day;
int month;
int year;
int hour;
int minute;
};
#define QT1X0_THUMB_WIDTH 80
#define QT1X0_THUMB_HEIGHT 60
#define QT1X0_THUMB_SIZE (QT1X0_THUMB_WIDTH * QT1X0_THUMB_HEIGHT / 2)
/* Decoders */
char *qtk_ppm_header(int width, int height);
void qtk_raw_header(unsigned char *data, const char *pic_format);
int qtk_ppm_size(int width, int height);
int qtk_thumbnail_decode(unsigned char *raw, unsigned char **out, Quicktake1x0Model model);
int qtkt_decode(unsigned char *raw, int width, int height, unsigned char **out);
int qtkn_decode(unsigned char *raw, int width, int height, unsigned char **out);
unsigned char getbithuff (int nbits, unsigned char **raw, unsigned short *huff);
#define ABS(x) (((int)(x) ^ ((int)(x) >> 31)) - ((int)(x) >> 31))
#define LIM(x,min,max) MAX(min,MIN(x,max))
#define RAW(ptr, row,col) (ptr)[(row)*width+(col)]
#define getbits(n, raw) getbithuff(n, raw, 0)
#endif /* !defined(CAMLIBS_QUICKTAKE_1X0_H) */

View File

@@ -555,6 +555,7 @@ GP_CAMLIB([polaroid_pdc320],[outdated])dnl
GP_CAMLIB([polaroid_pdc640],[outdated])dnl GP_CAMLIB([polaroid_pdc640],[outdated])dnl
GP_CAMLIB([polaroid_pdc700],[outdated])dnl GP_CAMLIB([polaroid_pdc700],[outdated])dnl
GP_CAMLIB([ptp2])dnl GP_CAMLIB([ptp2])dnl
GP_CAMLIB([quicktake1x0])dnl
GP_CAMLIB([ricoh],[outdated])dnl GP_CAMLIB([ricoh],[outdated])dnl
GP_CAMLIB([ricoh_g3])dnl GP_CAMLIB([ricoh_g3])dnl
GP_CAMLIB([samsung],[outdated])dnl GP_CAMLIB([samsung],[outdated])dnl

View File

@@ -43,6 +43,8 @@ extern "C" {
#define GP_MIME_JPEG "image/jpeg" #define GP_MIME_JPEG "image/jpeg"
#define GP_MIME_TIFF "image/tiff" #define GP_MIME_TIFF "image/tiff"
#define GP_MIME_BMP "image/bmp" #define GP_MIME_BMP "image/bmp"
#define GP_MIME_QTKN "image/qtkn"
#define GP_MIME_QTKT "image/qtkt"
#define GP_MIME_QUICKTIME "video/quicktime" #define GP_MIME_QUICKTIME "video/quicktime"
#define GP_MIME_AVI "video/x-msvideo" #define GP_MIME_AVI "video/x-msvideo"
#define GP_MIME_CRW "image/x-canon-raw" #define GP_MIME_CRW "image/x-canon-raw"

View File

@@ -596,6 +596,8 @@ static const char *mime_table[] = {
"arw", GP_MIME_ARW, "arw", GP_MIME_ARW,
"raf", GP_MIME_RAF, "raf", GP_MIME_RAF,
"txt", GP_MIME_TXT, "txt", GP_MIME_TXT,
"qtk", GP_MIME_QTKT,
"qtk", GP_MIME_QTKN,
NULL}; NULL};
/** /**
* @param file a #CameraFile * @param file a #CameraFile
@@ -1106,6 +1108,8 @@ gp_file_adjust_name_for_mime_type (CameraFile *file)
GP_MIME_ARW, "arw", GP_MIME_ARW, "arw",
GP_MIME_RAF, "raf", GP_MIME_RAF, "raf",
GP_MIME_TXT, "txt", GP_MIME_TXT, "txt",
GP_MIME_QTKT, "qtk",
GP_MIME_QTKN, "qtk",
NULL}; NULL};
C_PARAMS (file); C_PARAMS (file);