From 87ebc2af1f0417b3bc38a233e28ff673eff4fa51 Mon Sep 17 00:00:00 2001 From: Alessio Treglia Date: Sat, 27 Aug 2011 10:54:21 +0200 Subject: Imported Upstream version 3.1.5 --- src/page.vala | 735 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 735 insertions(+) create mode 100644 src/page.vala (limited to 'src/page.vala') diff --git a/src/page.vala b/src/page.vala new file mode 100644 index 0000000..ef7ddb6 --- /dev/null +++ b/src/page.vala @@ -0,0 +1,735 @@ +/* + * Copyright (C) 2009-2011 Canonical Ltd. + * Author: Robert Ancell + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. See http://www.gnu.org/copyleft/gpl.html the full text of the + * license. + */ + +public enum ScanDirection +{ + TOP_TO_BOTTOM, + LEFT_TO_RIGHT, + BOTTOM_TO_TOP, + RIGHT_TO_LEFT +} + +public class Page +{ + /* Resolution of page */ + private int dpi; + + /* Number of rows in this page or -1 if currently unknown */ + private int expected_rows; + + /* Bit depth */ + private int depth; + + /* Color profile */ + private string? color_profile; + + /* Scanned image data */ + private int width; + private int n_rows; + private int rowstride; + private int n_channels; + private uchar[] pixels; + + /* Page is getting data */ + private bool scanning; + + /* true if have some page data */ + private bool has_data_; + + /* Expected next scan row */ + private int scan_line; + + /* Rotation of scanned data */ + private ScanDirection scan_direction = ScanDirection.TOP_TO_BOTTOM; + + /* Crop */ + private bool has_crop_; + private string? crop_name; + private int crop_x; + private int crop_y; + private int crop_width; + private int crop_height; + + public signal void pixels_changed (); + public signal void size_changed (); + public signal void scan_line_changed (); + public signal void scan_direction_changed (); + public signal void crop_changed (); + + public Page (int width, int height, int dpi, ScanDirection scan_direction) + { + if (scan_direction == ScanDirection.TOP_TO_BOTTOM || scan_direction == ScanDirection.BOTTOM_TO_TOP) + { + this.width = width; + n_rows = height; + } + else + { + this.width = height; + n_rows = width; + } + this.dpi = dpi; + this.scan_direction = scan_direction; + } + + public void set_page_info (ScanPageInfo info) + { + expected_rows = info.height; + dpi = (int) info.dpi; + + /* Create a white page */ + width = info.width; + n_rows = info.height; + /* Variable height, try 50% of the width for now */ + if (n_rows < 0) + n_rows = width / 2; + depth = info.depth; + n_channels = info.n_channels; + rowstride = (width * depth * n_channels + 7) / 8; + pixels.resize (n_rows * rowstride); + return_if_fail (pixels != null); + + /* Fill with white */ + if (depth == 1) + Memory.set (pixels, 0x00, n_rows * rowstride); + else + Memory.set (pixels, 0xFF, n_rows * rowstride); + + size_changed (); + pixels_changed (); + } + + public void start () + { + scanning = true; + scan_line_changed (); + } + + public bool is_scanning () + { + return scanning; + } + + public bool has_data () + { + return has_data_; + } + + public bool is_color () + { + return n_channels > 1; + } + + public int get_scan_line () + { + return scan_line; + } + + private void parse_line (ScanLine line, int n, out bool size_changed) + { + int line_number; + + line_number = line.number + n; + + /* Extend image if necessary */ + size_changed = false; + while (line_number >= get_scan_height ()) + { + int rows; + + /* Extend image */ + rows = n_rows; + n_rows = rows + width / 2; + debug ("Extending image from %d lines to %d lines", rows, n_rows); + pixels.resize (n_rows * rowstride); + + size_changed = true; + } + + /* Copy in new row */ + var offset = line_number * rowstride; + var line_offset = n * line.data_length; + for (var i = 0; i < line.data_length; i++) + pixels[offset+i] = line.data[line_offset+i]; + + scan_line = line_number; + } + + public void parse_scan_line (ScanLine line) + { + bool size_has_changed = false; + for (var i = 0; i < line.n_lines; i++) + parse_line (line, i, out size_has_changed); + + has_data_ = true; + + if (size_has_changed) + size_changed (); + scan_line_changed (); + pixels_changed (); + } + + public void finish () + { + bool size_has_changed = false; + + /* Trim page */ + if (expected_rows < 0 && + scan_line != get_scan_height ()) + { + int rows; + + rows = n_rows; + n_rows = scan_line; + pixels.resize (n_rows * rowstride); + debug ("Trimming page from %d lines to %d lines", rows, n_rows); + + size_has_changed = true; + } + scanning = false; + + if (size_has_changed) + size_changed (); + scan_line_changed (); + } + + public ScanDirection get_scan_direction () + { + return scan_direction; + } + + private void set_scan_direction (ScanDirection direction) + { + int left_steps, t; + bool size_has_changed = false; + int width, height; + + if (scan_direction == direction) + return; + + /* Work out how many times it has been rotated to the left */ + left_steps = direction - scan_direction; + if (left_steps < 0) + left_steps += 4; + if (left_steps != 2) + size_has_changed = true; + + width = get_width (); + height = get_height (); + + /* Rotate crop */ + if (has_crop_) + { + switch (left_steps) + { + /* 90 degrees counter-clockwise */ + case 1: + t = crop_x; + crop_x = crop_y; + crop_y = width - (t + crop_width); + t = crop_width; + crop_width = crop_height; + crop_height = t; + break; + /* 180 degrees */ + case 2: + crop_x = width - (crop_x + crop_width); + crop_y = width - (crop_y + crop_height); + break; + /* 90 degrees clockwise */ + case 3: + t = crop_y; + crop_y = crop_x; + crop_x = height - (t + crop_height); + t = crop_width; + crop_width = crop_height; + crop_height = t; + break; + } + } + + scan_direction = direction; + if (size_has_changed) + size_changed (); + scan_direction_changed (); + if (has_crop_) + crop_changed (); + } + + public void rotate_left () + { + var direction = scan_direction; + switch (direction) + { + case ScanDirection.TOP_TO_BOTTOM: + direction = ScanDirection.LEFT_TO_RIGHT; + break; + case ScanDirection.LEFT_TO_RIGHT: + direction = ScanDirection.BOTTOM_TO_TOP; + break; + case ScanDirection.BOTTOM_TO_TOP: + direction = ScanDirection.RIGHT_TO_LEFT; + break; + case ScanDirection.RIGHT_TO_LEFT: + direction = ScanDirection.TOP_TO_BOTTOM; + break; + } + set_scan_direction (direction); + } + + public void rotate_right () + { + var direction = scan_direction; + switch (direction) + { + case ScanDirection.TOP_TO_BOTTOM: + direction = ScanDirection.RIGHT_TO_LEFT; + break; + case ScanDirection.LEFT_TO_RIGHT: + direction = ScanDirection.TOP_TO_BOTTOM; + break; + case ScanDirection.BOTTOM_TO_TOP: + direction = ScanDirection.LEFT_TO_RIGHT; + break; + case ScanDirection.RIGHT_TO_LEFT: + direction = ScanDirection.BOTTOM_TO_TOP; + break; + } + set_scan_direction (direction); + } + + public int get_dpi () + { + return dpi; + } + + public bool is_landscape () + { + return get_width () > get_height (); + } + + public int get_width () + { + if (scan_direction == ScanDirection.TOP_TO_BOTTOM || scan_direction == ScanDirection.BOTTOM_TO_TOP) + return width; + else + return n_rows; + } + + public int get_height () + { + if (scan_direction == ScanDirection.TOP_TO_BOTTOM || scan_direction == ScanDirection.BOTTOM_TO_TOP) + return n_rows; + else + return width; + } + + public int get_depth () + { + return depth; + } + + public int get_n_channels () + { + return n_channels; + } + + public int get_rowstride () + { + return rowstride; + } + + public int get_scan_width () + { + return width; + } + + public int get_scan_height () + { + return n_rows; + } + + public void set_color_profile (string? color_profile) + { + this.color_profile = color_profile; + } + + public string get_color_profile () + { + return color_profile; + } + + public void set_no_crop () + { + if (!has_crop_) + return; + has_crop_ = false; + crop_changed (); + } + + public void set_custom_crop (int width, int height) + { + //int pw, ph; + + return_if_fail (width >= 1); + return_if_fail (height >= 1); + + if (crop_name == null && has_crop_ && crop_width == width && crop_height == height) + return; + crop_name = null; + has_crop_ = true; + + crop_width = width; + crop_height = height; + + /*pw = get_width (); + ph = get_height (); + if (crop_width < pw) + crop_x = (pw - crop_width) / 2; + else + crop_x = 0; + if (crop_height < ph) + crop_y = (ph - crop_height) / 2; + else + crop_y = 0;*/ + + crop_changed (); + } + + public void set_named_crop (string name) + { + double width, height; + switch (name) + { + case "A4": + width = 8.3; + height = 11.7; + break; + case "A5": + width = 5.8; + height = 8.3; + break; + case "A6": + width = 4.1; + height = 5.8; + break; + case "letter": + width = 8.5; + height = 11; + break; + case "legal": + width = 8.5; + height = 14; + break; + case "4x6": + width = 4; + height = 6; + break; + default: + warning ("Unknown paper size '%s'", name); + return; + } + + crop_name = name; + has_crop_ = true; + + var pw = get_width (); + var ph = get_height (); + + /* Rotate to match original aspect */ + if (pw > ph) + { + double t; + t = width; + width = height; + height = t; + } + + /* Custom crop, make slightly smaller than original */ + crop_width = (int) (width * dpi + 0.5); + crop_height = (int) (height * dpi + 0.5); + + if (crop_width < pw) + crop_x = (pw - crop_width) / 2; + else + crop_x = 0; + if (crop_height < ph) + crop_y = (ph - crop_height) / 2; + else + crop_y = 0; + crop_changed (); + } + + public void move_crop (int x, int y) + { + return_if_fail (x >= 0); + return_if_fail (y >= 0); + return_if_fail (x < get_width ()); + return_if_fail (y < get_height ()); + + crop_x = x; + crop_y = y; + crop_changed (); + } + + public void rotate_crop () + { + int t; + + if (!has_crop_) + return; + + t = crop_width; + crop_width = crop_height; + crop_height = t; + + /* Clip custom crops */ + if (crop_name == null) + { + int w, h; + + w = get_width (); + h = get_height (); + + if (crop_x + crop_width > w) + crop_x = w - crop_width; + if (crop_x < 0) + { + crop_x = 0; + crop_width = w; + } + if (crop_y + crop_height > h) + crop_y = h - crop_height; + if (crop_y < 0) + { + crop_y = 0; + crop_height = h; + } + } + + crop_changed (); + } + + public bool has_crop () + { + return has_crop_; + } + + public void get_crop (out int x, out int y, out int width, out int height) + { + x = crop_x; + y = crop_y; + width = crop_width; + height = crop_height; + } + + public string get_named_crop () + { + return crop_name; + } + + public unowned uchar[] get_pixels () + { + return pixels; + } + + // FIXME: Copied from page-view, should be shared code + private uchar get_sample (uchar[] pixels, int offset, int x, int depth, int n_channels, int channel) + { + // FIXME + return 0xFF; + } + + // FIXME: Copied from page-view, should be shared code + private void get_pixel (int x, int y, uchar[] pixel, int offset) + { + switch (get_scan_direction ()) + { + case ScanDirection.TOP_TO_BOTTOM: + break; + case ScanDirection.BOTTOM_TO_TOP: + x = get_scan_width () - x - 1; + y = get_scan_height () - y - 1; + break; + case ScanDirection.LEFT_TO_RIGHT: + var t = x; + x = get_scan_width () - y - 1; + y = t; + break; + case ScanDirection.RIGHT_TO_LEFT: + var t = x; + x = y; + y = get_scan_height () - t - 1; + break; + } + + var depth = get_depth (); + var n_channels = get_n_channels (); + var line_offset = get_rowstride () * y; + + /* Optimise for 8 bit images */ + if (depth == 8 && n_channels == 3) + { + var o = line_offset + x * n_channels; + pixel[offset+0] = pixels[o]; + pixel[offset+1] = pixels[o+1]; + pixel[offset+2] = pixels[o+2]; + return; + } + else if (depth == 8 && n_channels == 1) + { + var p = pixels[line_offset + x]; + pixel[offset+0] = pixel[offset+1] = pixel[offset+2] = p; + return; + } + + /* Optimise for bitmaps */ + else if (depth == 1 && n_channels == 1) + { + var p = pixels[line_offset + (x / 8)]; + pixel[offset+0] = pixel[offset+1] = pixel[offset+2] = (p & (0x80 >> (x % 8))) != 0 ? 0x00 : 0xFF; + return; + } + + /* Optimise for 2 bit images */ + else if (depth == 2 && n_channels == 1) + { + int block_shift[4] = { 6, 4, 2, 0 }; + + var p = pixels[line_offset + (x / 4)]; + var sample = (p >> block_shift[x % 4]) & 0x3; + sample = sample * 255 / 3; + + pixel[offset+0] = pixel[offset+1] = pixel[offset+2] = (uchar) sample; + return; + } + + /* Use slow method */ + pixel[offset+0] = get_sample (pixels, line_offset, x, depth, n_channels, 0); + pixel[offset+1] = get_sample (pixels, line_offset, x, depth, n_channels, 1); + pixel[offset+2] = get_sample (pixels, line_offset, x, depth, n_channels, 2); + } + + public Gdk.Pixbuf get_image (bool apply_crop) + { + int l, r, t, b; + if (apply_crop && has_crop_) + { + l = crop_x; + r = l + crop_width; + t = crop_y; + b = t + crop_height; + + if (l < 0) + l = 0; + if (r > get_width ()) + r = get_width (); + if (t < 0) + t = 0; + if (b > get_height ()) + b = get_height (); + } + else + { + l = 0; + r = get_width (); + t = 0; + b = get_height (); + } + + var image = new Gdk.Pixbuf (Gdk.Colorspace.RGB, false, 8, r - l, b - t); + unowned uint8[] image_pixels = image.get_pixels (); + for (var y = t; y < b; y++) + { + var offset = image.get_rowstride () * (y - t); + for (var x = l; x < r; x++) + get_pixel (x, y, image_pixels, offset + (x - l) * 3); + } + + return image; + } + + private string? get_icc_data_encoded (string icc_profile_filename) + { + /* Get binary data */ + string contents; + try + { + FileUtils.get_contents (icc_profile_filename, out contents); + } + catch (Error e) + { + warning ("failed to get icc profile data: %s", e.message); + return null; + } + + /* Encode into base64 */ + return Base64.encode ((uchar[]) contents.to_utf8 ()); + } + + public void save (string type, File file) throws Error + { + var stream = file.replace (null, false, FileCreateFlags.NONE, null); + var writer = new PixbufWriter (stream); + var image = get_image (true); + + string? icc_profile_data = null; + if (color_profile != null) + icc_profile_data = get_icc_data_encoded (color_profile); + + if (strcmp (type, "jpeg") == 0) + { + /* ICC profile is awaiting review in gtk2+ bugzilla */ + string[] keys = { "quality", /* "icc-profile", */ null }; + string[] values = { "90", /* icc_profile_data, */ null }; + writer.save (image, "jpeg", keys, values); + } + else if (strcmp (type, "png") == 0) + { + string[] keys = { "icc-profile", null }; + string[] values = { icc_profile_data, null }; + if (icc_profile_data == null) + keys[0] = null; + writer.save (image, "png", keys, values); + } + else if (strcmp (type, "tiff") == 0) + { + string[] keys = { "compression", "icc-profile", null }; + string[] values = { "8" /* Deflate compression */, icc_profile_data, null }; + if (icc_profile_data == null) + keys[1] = null; + writer.save (image, "tiff", keys, values); + } + else + ; // FIXME: Throw Error + } +} + +public class PixbufWriter +{ + public FileOutputStream stream; + + public PixbufWriter (FileOutputStream stream) + { + this.stream = stream; + } + + public void save (Gdk.Pixbuf image, string type, string[] option_keys, string[] option_values) throws Error + { + image.save_to_callbackv (write_pixbuf_data, type, option_keys, option_values); + } + + private bool write_pixbuf_data (uint8[] buf) throws Error + { + stream.write_all (buf, null, null); + return true; + } +} -- cgit v1.2.3