~twright-tdw/simple-scan/toolbar-improvements

« back to all changes in this revision

Viewing changes to src/simple-scan.c

  • Committer: robert.ancell at gmail
  • Date: 2009-11-13 07:15:15 UTC
  • Revision ID: robert.ancell@gmail.com-20091113071515-dwpsirvyv9rn6um0
Initial simple-scan release

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#include <stdio.h>
 
2
#include <string.h>
 
3
#include <gtk/gtk.h>
 
4
#include <cairo/cairo-pdf.h>
 
5
#include <math.h>
 
6
 
 
7
#include "ui.h"
 
8
#include "scanner.h"
 
9
 
 
10
#define DEFAULT_DPI 75 // FIXME
 
11
 
 
12
static SimpleScan *ui;
 
13
 
 
14
static Scanner *scanner;
 
15
 
 
16
static GdkPixbuf *raw_image = NULL;
 
17
 
 
18
static gboolean scan_complete = FALSE;
 
19
 
 
20
static int current_line;
 
21
 
 
22
 
 
23
static void
 
24
update_scan_devices_cb (Scanner *scanner, GList *devices)
 
25
{
 
26
    GList *dev_iter;
 
27
 
 
28
    /* Mark existing values as undetected */
 
29
    ui_mark_devices_undetected (ui);
 
30
 
 
31
    /* Add/update detected devices */
 
32
    for (dev_iter = devices; dev_iter; dev_iter = dev_iter->next) {
 
33
        ScanDevice *device = dev_iter->data;
 
34
        
 
35
        ui_add_scan_device (ui, device->name, device->label);
 
36
        g_free (device->name);
 
37
        g_free (device->label);
 
38
        g_free (device);
 
39
    }
 
40
    g_list_free (devices);
 
41
}
 
42
 
 
43
 
 
44
static void
 
45
scanner_page_info_cb (Scanner *scanner, ScanPageInfo *info)
 
46
{
 
47
    gint height;
 
48
 
 
49
    g_debug ("Page is %d pixels wide, %d pixels high, %d bits per pixel",
 
50
             info->width, info->height, info->depth);
 
51
 
 
52
    /* Variable heigh, try 50% of the width for now */
 
53
    if (info->height < 0)
 
54
        height = info->width / 2;
 
55
    else
 
56
        height = info->height;
 
57
 
 
58
    raw_image = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE,
 
59
                                info->depth,
 
60
                                info->width,
 
61
                                height);
 
62
    g_free (info);
 
63
 
 
64
    current_line = 0;
 
65
    scan_complete = FALSE;
 
66
}
 
67
 
 
68
 
 
69
static int
 
70
get_sample (guchar *data, int depth, int index)
 
71
{
 
72
    int i, offset, value, n_bits;
 
73
 
 
74
    /* Optimise if using 8 bit samples */
 
75
    if (depth == 8)
 
76
        return data[index];
 
77
 
 
78
    /* Bit offset for this sample */
 
79
    offset = depth * index;
 
80
 
 
81
    /* Get the remaining bits in the octet this sample starts in */
 
82
    i = offset / 8;
 
83
    n_bits = 8 - offset % 8;
 
84
    value = data[i] & (0xFF >> (8 - n_bits));
 
85
    
 
86
    /* Add additional octets until get enough bits */
 
87
    while (n_bits < depth) {
 
88
        value = value << 8 | data[i++];
 
89
        n_bits += 8;
 
90
    }
 
91
 
 
92
    /* Trim remaining bits off */
 
93
    if (n_bits > depth)
 
94
        value >>= n_bits - depth;
 
95
    
 
96
    return value;
 
97
}
 
98
 
 
99
 
 
100
static void
 
101
scanner_line_cb (Scanner *scanner, ScanLine *line)
 
102
{
 
103
    guchar *pixels;
 
104
    gint i, j;
 
105
    
 
106
    /* Extend image if necessary */
 
107
    while (line->number >= gdk_pixbuf_get_height (raw_image)) {
 
108
        GdkPixbuf *image;
 
109
        gint height, width, new_height;
 
110
 
 
111
        width = gdk_pixbuf_get_width (raw_image);        
 
112
        height = gdk_pixbuf_get_height (raw_image);
 
113
        new_height = height + width / 2;
 
114
        g_debug("Resizing image height from %d pixels to %d pixels", height, new_height);
 
115
 
 
116
        image = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE,
 
117
                                gdk_pixbuf_get_bits_per_sample (raw_image),
 
118
                                width, new_height);
 
119
        memcpy (gdk_pixbuf_get_pixels (image),
 
120
                gdk_pixbuf_get_pixels (raw_image),
 
121
                height * gdk_pixbuf_get_rowstride (raw_image));
 
122
        g_object_unref (raw_image);
 
123
        raw_image = image;
 
124
    }
 
125
 
 
126
    pixels = gdk_pixbuf_get_pixels (raw_image) + line->number * gdk_pixbuf_get_rowstride (raw_image);
 
127
    switch (line->format) {
 
128
    case LINE_RGB:
 
129
        memcpy (pixels, line->data, line->data_length);
 
130
        break;
 
131
    case LINE_GRAY:
 
132
        for (i = 0; i < line->width; i++) {
 
133
            pixels[j] = get_sample (line->data, line->depth, i);
 
134
            pixels[j+1] = get_sample (line->data, line->depth, i);
 
135
            pixels[j+2] = get_sample (line->data, line->depth, i);
 
136
            j += 3;
 
137
        }
 
138
        break;
 
139
    case LINE_RED:
 
140
        for (i = 0; i < line->width; i++) {
 
141
            pixels[j] = get_sample (line->data, line->depth, i);
 
142
            j += 3;
 
143
        }
 
144
        break;
 
145
    case LINE_GREEN:
 
146
        for (i = 0; i < line->width; i++) {
 
147
            pixels[j+1] = get_sample (line->data, line->depth, i);
 
148
            j += 3;
 
149
        }
 
150
        break;
 
151
    case LINE_BLUE:
 
152
        for (i = 0; i < line->width; i++) {
 
153
            pixels[j+2] = get_sample (line->data, line->depth, i);
 
154
            j += 3;
 
155
        }
 
156
        break;
 
157
    }
 
158
 
 
159
    current_line = line->number + 1;
 
160
 
 
161
    g_free(line->data);
 
162
    g_free(line);
 
163
 
 
164
    ui_redraw_preview (ui);
 
165
}
 
166
 
 
167
 
 
168
static void
 
169
scanner_image_done_cb (Scanner *scanner)
 
170
{
 
171
    /* Trim image */
 
172
    if (current_line != gdk_pixbuf_get_height (raw_image)) {
 
173
        GdkPixbuf *image;
 
174
 
 
175
        gint height, width, new_height;
 
176
 
 
177
        width = gdk_pixbuf_get_width (raw_image);        
 
178
        height = gdk_pixbuf_get_height (raw_image);
 
179
        new_height = current_line;
 
180
        g_debug("Trimming image height from %d pixels to %d pixels", height, new_height);
 
181
 
 
182
        image = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE,
 
183
                                gdk_pixbuf_get_bits_per_sample (raw_image),
 
184
                                width, new_height);
 
185
        memcpy (gdk_pixbuf_get_pixels (image),
 
186
                gdk_pixbuf_get_pixels (raw_image),
 
187
                new_height * gdk_pixbuf_get_rowstride (raw_image));
 
188
        g_object_unref (raw_image);
 
189
        raw_image = image;
 
190
    }
 
191
    
 
192
    scan_complete = TRUE;
 
193
    ui_redraw_preview (ui);
 
194
    ui_set_have_scan (ui, TRUE);
 
195
}
 
196
 
 
197
 
 
198
static void
 
199
scanner_ready_cb (Scanner *scanner)
 
200
{
 
201
    ui_set_scanning (ui, FALSE);
 
202
}
 
203
 
 
204
 
 
205
static void
 
206
render_scan (cairo_t *context, GdkPixbuf *image, Orientation orientation, double canvas_width, double canvas_height, gboolean show_scan_line)
 
207
{
 
208
    double orig_img_width, orig_img_height, img_width, img_height;
 
209
    double source_aspect, canvas_aspect;
 
210
    double x_offset = 0.0, y_offset = 0.0, scale = 1.0, rotation = 0.0;
 
211
 
 
212
    orig_img_width = img_width = gdk_pixbuf_get_width (image);
 
213
    orig_img_height = img_height = gdk_pixbuf_get_height (image);
 
214
 
 
215
    switch (orientation) {
 
216
    case TOP_TO_BOTTOM:
 
217
        rotation = 0.0;
 
218
        break;
 
219
    case BOTTOM_TO_TOP:
 
220
        rotation = M_PI;
 
221
        break;
 
222
    case LEFT_TO_RIGHT:
 
223
        img_width = orig_img_height;
 
224
        img_height = orig_img_width;
 
225
        rotation = -M_PI_2;
 
226
        break;
 
227
    case RIGHT_TO_LEFT:
 
228
        img_width = orig_img_height;
 
229
        img_height = orig_img_width;
 
230
        rotation = M_PI_2;
 
231
        break;
 
232
    }
 
233
 
 
234
    /* Scale if cannot fit into canvas */
 
235
    if (img_width > canvas_width || img_height > canvas_height) {
 
236
        canvas_aspect = canvas_width / canvas_height;
 
237
        source_aspect = img_width / img_height;
 
238
 
 
239
        /* Scale to canvas height */
 
240
        if (canvas_aspect > source_aspect) {
 
241
            scale = canvas_height / img_height;
 
242
            x_offset = (int) (canvas_width - (img_width * scale)) / 2;
 
243
        }
 
244
        /* Otherwise scale to canvas width */
 
245
        else {
 
246
            scale = canvas_width / img_width;
 
247
            y_offset = (int) (canvas_height - (img_height * scale)) / 2;
 
248
        }
 
249
    }
 
250
    
 
251
    /* Render the image */
 
252
    cairo_save (context);
 
253
 
 
254
    cairo_translate (context, x_offset, y_offset);
 
255
    cairo_scale (context, scale, scale);
 
256
    cairo_translate (context, img_width / 2, img_height / 2);
 
257
    cairo_rotate (context, rotation);
 
258
    cairo_translate (context, -orig_img_width / 2, -orig_img_height / 2);
 
259
 
 
260
    gdk_cairo_set_source_pixbuf (context, image, 0, 0);
 
261
    cairo_pattern_set_filter (cairo_get_source (context), CAIRO_FILTER_BEST);
 
262
    cairo_paint (context);
 
263
 
 
264
    cairo_restore (context);
 
265
 
 
266
    /* Show scan line */
 
267
    if (show_scan_line && !scan_complete) {
 
268
        double h = scale * (double)(current_line * orig_img_height) / (double)img_height;
 
269
        
 
270
        switch (orientation) {
 
271
        case TOP_TO_BOTTOM:
 
272
            cairo_translate (context, x_offset, y_offset);
 
273
            break;
 
274
        case BOTTOM_TO_TOP:
 
275
            cairo_translate (context, canvas_width - x_offset, canvas_height - y_offset);
 
276
            break;
 
277
        case LEFT_TO_RIGHT:
 
278
            cairo_translate (context, x_offset, canvas_height - y_offset);
 
279
            break;
 
280
        case RIGHT_TO_LEFT:
 
281
            cairo_translate (context, canvas_width - x_offset, y_offset);
 
282
            break;
 
283
        }
 
284
        cairo_rotate (context, rotation);
 
285
 
 
286
        cairo_set_source_rgb (context, 1.0, 0.0, 0.0);
 
287
        cairo_move_to (context, 0, h);
 
288
        cairo_line_to (context, scale * orig_img_width, h);
 
289
        cairo_stroke (context);
 
290
    }
 
291
}
 
292
 
 
293
 
 
294
static void
 
295
render_cb (SimpleScan *ui, cairo_t *context, double width, double height)
 
296
{
 
297
    if (raw_image) {
 
298
        render_scan (context, raw_image, ui_get_orientation (ui),
 
299
                     width, height, TRUE);
 
300
    }
 
301
    else {
 
302
        cairo_set_source_rgb (context, 0.0, 0.0, 0.0);
 
303
        cairo_rectangle (context, 0, 0, width, height);
 
304
        cairo_fill (context);
 
305
    }
 
306
}
 
307
 
 
308
 
 
309
static void
 
310
scan_cb (SimpleScan *ui, const gchar *device)
 
311
{
 
312
    g_debug ("Requesting scan from device '%s'", device);
 
313
    ui_set_have_scan (ui, FALSE);
 
314
    ui_set_scanning (ui, TRUE);
 
315
    scanner_scan (scanner, device, DEFAULT_DPI);
 
316
}
 
317
 
 
318
 
 
319
static void
 
320
cancel_cb (SimpleScan *ui)
 
321
{
 
322
    scanner_cancel (scanner);
 
323
}
 
324
 
 
325
 
 
326
static gboolean
 
327
write_pixbuf_data (const gchar *buf, gsize count, GError **error, GFileOutputStream *stream)
 
328
{
 
329
    return g_output_stream_write_all (G_OUTPUT_STREAM (stream), buf, count, NULL, NULL, error);
 
330
}
 
331
 
 
332
 
 
333
static gboolean
 
334
save_jpeg (GdkPixbuf *image, GFileOutputStream *stream, GError **error)
 
335
{
 
336
    return gdk_pixbuf_save_to_callback (image,
 
337
                                        (GdkPixbufSaveFunc) write_pixbuf_data, stream,
 
338
                                        "jpeg", error,
 
339
                                        "quality", "90",
 
340
                                        NULL);
 
341
}
 
342
 
 
343
 
 
344
static gboolean
 
345
save_png (GdkPixbuf *image, GFileOutputStream *stream, GError **error)
 
346
{
 
347
    return gdk_pixbuf_save_to_callback (image,
 
348
                                        (GdkPixbufSaveFunc) write_pixbuf_data, stream,
 
349
                                        "png", error,
 
350
                                        NULL);
 
351
}
 
352
 
 
353
    
 
354
static cairo_status_t
 
355
write_pdf_data (GFileOutputStream *stream, unsigned char *data, unsigned int length)
 
356
{
 
357
    gboolean result;
 
358
    GError *error = NULL;
 
359
 
 
360
    result = g_output_stream_write_all (G_OUTPUT_STREAM (stream), data, length, NULL, NULL, &error);
 
361
    
 
362
    if (error) {
 
363
        g_warning ("Error writing PDF data: %s", error->message);
 
364
        g_error_free (error);
 
365
    }
 
366
 
 
367
    return result ? CAIRO_STATUS_SUCCESS : CAIRO_STATUS_WRITE_ERROR;
 
368
}
 
369
   
 
370
 
 
371
static gboolean
 
372
save_pdf (GdkPixbuf *image, GFileOutputStream *stream, GError **error)
 
373
{
 
374
    cairo_surface_t *surface;
 
375
    cairo_t *context;
 
376
    double width, height;
 
377
    
 
378
    width = gdk_pixbuf_get_width (image) * 72.0 / DEFAULT_DPI;
 
379
    height = gdk_pixbuf_get_height (image) * 72.0 / DEFAULT_DPI;
 
380
 
 
381
    surface = cairo_pdf_surface_create_for_stream ((cairo_write_func_t) write_pdf_data,
 
382
                                                   stream,
 
383
                                                   width, height);
 
384
    
 
385
    context = cairo_create (surface);
 
386
 
 
387
    cairo_scale (context, 72.0 / DEFAULT_DPI, 72.0 / DEFAULT_DPI);
 
388
    gdk_cairo_set_source_pixbuf (context, image, 0, 0);
 
389
    cairo_pattern_set_filter (cairo_get_source (context), CAIRO_FILTER_BEST);
 
390
    cairo_paint (context);
 
391
 
 
392
    cairo_destroy (context);
 
393
    cairo_surface_destroy (surface);
 
394
    
 
395
    return TRUE;
 
396
}
 
397
 
 
398
 
 
399
static GdkPixbuf *get_rotated_image (Orientation orientation)
 
400
{
 
401
    switch (orientation) {
 
402
    default:
 
403
    case TOP_TO_BOTTOM:
 
404
        return gdk_pixbuf_ref (raw_image);
 
405
    case BOTTOM_TO_TOP:
 
406
        return gdk_pixbuf_rotate_simple (raw_image, GDK_PIXBUF_ROTATE_UPSIDEDOWN);
 
407
    case LEFT_TO_RIGHT:
 
408
        return gdk_pixbuf_rotate_simple (raw_image, GDK_PIXBUF_ROTATE_COUNTERCLOCKWISE);
 
409
    case RIGHT_TO_LEFT:
 
410
        return gdk_pixbuf_rotate_simple (raw_image, GDK_PIXBUF_ROTATE_CLOCKWISE);
 
411
    }   
 
412
}
 
413
 
 
414
 
 
415
static void
 
416
save_cb (SimpleScan *ui, gchar *uri)
 
417
{
 
418
    GFile *file;
 
419
    GError *error = NULL;
 
420
    GFileOutputStream *stream;
 
421
 
 
422
    file = g_file_new_for_uri (uri);
 
423
 
 
424
    stream = g_file_replace (file, NULL, FALSE, G_FILE_CREATE_NONE, NULL, &error);
 
425
    if (!stream) {
 
426
        // ...
 
427
        g_error_free (error);
 
428
    }
 
429
    else {
 
430
        gboolean result;
 
431
        gchar *uri_lower;
 
432
        GdkPixbuf *image;
 
433
 
 
434
        image = get_rotated_image (ui_get_orientation (ui));
 
435
 
 
436
        uri_lower = g_utf8_strdown (uri, -1);
 
437
        if (g_str_has_suffix (uri_lower, ".pdf"))
 
438
            result = save_pdf (image, stream, &error);
 
439
        else if (g_str_has_suffix (uri_lower, ".png"))
 
440
            result = save_png (image, stream, &error);
 
441
        else
 
442
            result = save_jpeg (image, stream, &error);
 
443
 
 
444
        g_free (uri_lower);           
 
445
        g_object_unref (image);
 
446
 
 
447
        if (!result) {
 
448
            // ...
 
449
            g_error_free (error);
 
450
        }
 
451
    }
 
452
}
 
453
 
 
454
 
 
455
static void
 
456
print_cb (SimpleScan *ui, cairo_t *context)
 
457
{
 
458
    GdkPixbuf *image;
 
459
 
 
460
    image = get_rotated_image (ui_get_orientation (ui));
 
461
 
 
462
    gdk_cairo_set_source_pixbuf (context, image, 0, 0);
 
463
    cairo_pattern_set_filter (cairo_get_source (context), CAIRO_FILTER_BEST);
 
464
    cairo_paint (context);
 
465
 
 
466
    g_object_unref (image);
 
467
}
 
468
 
 
469
 
 
470
int
 
471
main(int argc, char **argv)
 
472
{
 
473
    g_thread_init (NULL);    
 
474
    gtk_init (&argc, &argv);
 
475
 
 
476
    scanner = scanner_new ();
 
477
    g_signal_connect (G_OBJECT (scanner), "ready", G_CALLBACK (scanner_ready_cb), NULL);
 
478
    g_signal_connect (G_OBJECT (scanner), "update-devices", G_CALLBACK (update_scan_devices_cb), NULL);
 
479
    g_signal_connect (G_OBJECT (scanner), "got-page-info", G_CALLBACK (scanner_page_info_cb), NULL);
 
480
    g_signal_connect (G_OBJECT (scanner), "got-line", G_CALLBACK (scanner_line_cb), NULL);
 
481
    g_signal_connect (G_OBJECT (scanner), "image-done", G_CALLBACK (scanner_image_done_cb), NULL);
 
482
 
 
483
    ui = ui_new ();
 
484
    g_signal_connect (ui, "render-preview", G_CALLBACK (render_cb), NULL);
 
485
    g_signal_connect (ui, "start-scan", G_CALLBACK (scan_cb), NULL);
 
486
    g_signal_connect (ui, "stop-scan", G_CALLBACK (cancel_cb), NULL);
 
487
    g_signal_connect (ui, "save", G_CALLBACK (save_cb), NULL);
 
488
    g_signal_connect (ui, "print", G_CALLBACK (print_cb), NULL);
 
489
 
 
490
    gtk_main ();
 
491
 
 
492
    return 0;
 
493
}