diff --git a/AUTHORS b/AUTHORS index a5db0b197..966dc4cc5 100644 --- a/AUTHORS +++ b/AUTHORS @@ -84,6 +84,9 @@ scanner. Hans de Goede ST2205 Picture Frame support (and usbdiskdirect port driver). +Colin Leroy-Mira +Apple QuickTake 1x0 driver. + ========================================================================= git migration authors diff --git a/camlibs/Makefile.am b/camlibs/Makefile.am index c71aad128..01a289cfa 100644 --- a/camlibs/Makefile.am +++ b/camlibs/Makefile.am @@ -132,6 +132,7 @@ include pccam600/Makefile-files include pentax/Makefile-files include polaroid/Makefile-files include ptp2/Makefile-files +include quicktake1x0/Makefile-files include ricoh/Makefile-files include samsung/Makefile-files include sierra/Makefile-files diff --git a/camlibs/quicktake1x0/Makefile-files b/camlibs/quicktake1x0/Makefile-files new file mode 100644 index 000000000..e30b12689 --- /dev/null +++ b/camlibs/quicktake1x0/Makefile-files @@ -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) diff --git a/camlibs/quicktake1x0/qtk-helpers.c b/camlibs/quicktake1x0/qtk-helpers.c new file mode 100644 index 000000000..bd30332f5 --- /dev/null +++ b/camlibs/quicktake1x0/qtk-helpers.c @@ -0,0 +1,106 @@ +/* qtk-helpers.c + * + Copyright 1997-2018 by Dave Coffin, dcoffin a cybercom o net + * Copyright 2023, Colin Leroy-Mira + * + * 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 +#include +#include + +#include +#include + +/* 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; +} diff --git a/camlibs/quicktake1x0/qtk-thumbnail-decoder.c b/camlibs/quicktake1x0/qtk-thumbnail-decoder.c new file mode 100644 index 000000000..b3c0afdb4 --- /dev/null +++ b/camlibs/quicktake1x0/qtk-thumbnail-decoder.c @@ -0,0 +1,162 @@ +/* qtkt-decoder.c + * + * Copyright 1997-2018 by Dave Coffin, dcoffin a cybercom o net + * Copyright 2023, Colin Leroy-Mira + * + * 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 +#include +#include + +#include + +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); + } +} diff --git a/camlibs/quicktake1x0/qtkn-decoder.c b/camlibs/quicktake1x0/qtkn-decoder.c new file mode 100644 index 000000000..fe7f98406 --- /dev/null +++ b/camlibs/quicktake1x0/qtkn-decoder.c @@ -0,0 +1,207 @@ +/* qtkn-decoder.c + * + Copyright 1997-2018 by Dave Coffin, dcoffin a cybercom o net + * Copyright 2023, Colin Leroy-Mira + * + * 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 +#include +#include + +#include +#include + +#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 diff --git a/camlibs/quicktake1x0/qtkt-decoder.c b/camlibs/quicktake1x0/qtkt-decoder.c new file mode 100644 index 000000000..bad081b7b --- /dev/null +++ b/camlibs/quicktake1x0/qtkt-decoder.c @@ -0,0 +1,122 @@ +/* qtkt-decoder.c + * + Copyright 1997-2018 by Dave Coffin, dcoffin a cybercom o net + * Copyright 2023, Colin Leroy-Mira + * + * 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 +#include +#include + +#include +#include + +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; +} diff --git a/camlibs/quicktake1x0/quicktake1x0.c b/camlibs/quicktake1x0/quicktake1x0.c new file mode 100644 index 000000000..ee8e88b1b --- /dev/null +++ b/camlibs/quicktake1x0/quicktake1x0.c @@ -0,0 +1,927 @@ +/* quicktake1x0.c + * + * Copyright 2023, Colin Leroy-Mira + * 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 +#include +#include +#include + +#include + +#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 .\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); +} diff --git a/camlibs/quicktake1x0/quicktake1x0.h b/camlibs/quicktake1x0/quicktake1x0.h new file mode 100644 index 000000000..b691f0d91 --- /dev/null +++ b/camlibs/quicktake1x0/quicktake1x0.h @@ -0,0 +1,82 @@ +/* quicktake1x0.h + * + * Copyright 2023 Colin Leroy-Mira + * + * 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) */ diff --git a/configure.ac b/configure.ac index b4ad18142..090b75485 100644 --- a/configure.ac +++ b/configure.ac @@ -555,6 +555,7 @@ GP_CAMLIB([polaroid_pdc320],[outdated])dnl GP_CAMLIB([polaroid_pdc640],[outdated])dnl GP_CAMLIB([polaroid_pdc700],[outdated])dnl GP_CAMLIB([ptp2])dnl +GP_CAMLIB([quicktake1x0])dnl GP_CAMLIB([ricoh],[outdated])dnl GP_CAMLIB([ricoh_g3])dnl GP_CAMLIB([samsung],[outdated])dnl diff --git a/gphoto2/gphoto2-file.h b/gphoto2/gphoto2-file.h index f0cda32c2..a2a6889b1 100644 --- a/gphoto2/gphoto2-file.h +++ b/gphoto2/gphoto2-file.h @@ -43,6 +43,8 @@ extern "C" { #define GP_MIME_JPEG "image/jpeg" #define GP_MIME_TIFF "image/tiff" #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_AVI "video/x-msvideo" #define GP_MIME_CRW "image/x-canon-raw" diff --git a/libgphoto2/gphoto2-file.c b/libgphoto2/gphoto2-file.c index 40033b94d..b2b64ae66 100644 --- a/libgphoto2/gphoto2-file.c +++ b/libgphoto2/gphoto2-file.c @@ -596,6 +596,8 @@ static const char *mime_table[] = { "arw", GP_MIME_ARW, "raf", GP_MIME_RAF, "txt", GP_MIME_TXT, + "qtk", GP_MIME_QTKT, + "qtk", GP_MIME_QTKN, NULL}; /** * @param file a #CameraFile @@ -1106,6 +1108,8 @@ gp_file_adjust_name_for_mime_type (CameraFile *file) GP_MIME_ARW, "arw", GP_MIME_RAF, "raf", GP_MIME_TXT, "txt", + GP_MIME_QTKT, "qtk", + GP_MIME_QTKN, "qtk", NULL}; C_PARAMS (file);