/* * Copyright (C) 2009-2015 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 CropLocation { NONE = 0, MIDDLE, TOP, BOTTOM, LEFT, RIGHT, TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT } public class PageView : Object { /* Page being rendered */ public Page page { get; private set; } /* Image to render at current resolution */ private Gdk.Pixbuf? image = null; /* Border around image */ private bool selected_ = false; public bool selected { get { return selected_; } set { if ((this.selected && selected) || (!this.selected && !selected)) return; this.selected = selected; changed (); } } private int ruler_width = 8; private int border_width = 2; /* True if image needs to be regenerated */ private bool update_image = true; /* Direction of currently scanned image */ private ScanDirection scan_direction; /* Next scan line to render */ private int scan_line; /* Dimensions of image to generate */ private int width_; private int height_; /* Location to place this page */ public int x_offset { get; set; } public int y_offset { get; set; } private CropLocation crop_location; private double selected_crop_px; private double selected_crop_py; private int selected_crop_x; private int selected_crop_y; private int selected_crop_w; private int selected_crop_h; /* Cursor over this page */ public Gdk.CursorType cursor { get; private set; default = Gdk.CursorType.ARROW; } private int animate_n_segments = 7; private int animate_segment; private uint animate_timeout; public signal void size_changed (); public signal void changed (); public PageView (Page page) { this.page = page; page.pixels_changed.connect (page_pixels_changed_cb); page.size_changed.connect (page_size_changed_cb); page.crop_changed.connect (page_overlay_changed_cb); page.scan_line_changed.connect (page_overlay_changed_cb); page.scan_direction_changed.connect (scan_direction_changed_cb); } ~PageView () { page.pixels_changed.disconnect (page_pixels_changed_cb); page.size_changed.disconnect (page_size_changed_cb); page.crop_changed.disconnect (page_overlay_changed_cb); page.scan_line_changed.disconnect (page_overlay_changed_cb); page.scan_direction_changed.disconnect (scan_direction_changed_cb); } private uchar get_sample (uchar[] pixels, int offset, int x, int depth, int sample) { // FIXME return 0xFF; } private void get_pixel (Page page, int x, int y, uchar[] pixel) { switch (page.scan_direction) { case ScanDirection.TOP_TO_BOTTOM: break; case ScanDirection.BOTTOM_TO_TOP: x = page.scan_width - x - 1; y = page.scan_height - y - 1; break; case ScanDirection.LEFT_TO_RIGHT: var t = x; x = page.scan_width - y - 1; y = t; break; case ScanDirection.RIGHT_TO_LEFT: var t = x; x = y; y = page.scan_height - t - 1; break; } var depth = page.depth; var n_channels = page.n_channels; unowned uchar[] pixels = page.get_pixels (); var offset = page.rowstride * y; /* Optimise for 8 bit images */ if (depth == 8 && n_channels == 3) { var o = offset + x * n_channels; pixel[0] = pixels[o]; pixel[1] = pixels[o+1]; pixel[2] = pixels[o+2]; return; } else if (depth == 8 && n_channels == 1) { pixel[0] = pixel[1] = pixel[2] = pixels[offset + x]; return; } /* Optimise for bitmaps */ else if (depth == 1 && n_channels == 1) { var o = offset + (x / 8); pixel[0] = pixel[1] = pixel[2] = (pixels[o] & (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 o = offset + (x / 4); var sample = (pixels[o] >> block_shift[x % 4]) & 0x3; sample = sample * 255 / 3; pixel[0] = pixel[1] = pixel[2] = (uchar) sample; return; } /* Use slow method */ pixel[0] = get_sample (pixels, offset, x, depth, x * n_channels); pixel[1] = get_sample (pixels, offset, x, depth, x * n_channels + 1); pixel[2] = get_sample (pixels, offset, x, depth, x * n_channels + 2); } private void set_pixel (Page page, double l, double r, double t, double b, uchar[] output, int offset) { /* Decimation: * * Target pixel is defined by (t,l)-(b,r) * It touches 16 pixels in original image * It completely covers 4 pixels in original image (T,L)-(B,R) * Add covered pixels and add weighted partially covered pixels. * Divide by total area. * * l L R r * +-----+-----+-----+-----+ * | | | | | * t | +--+-----+-----+---+ | * T +--+--+-----+-----+---+-+ * | | | | | | | * | | | | | | | * +--+--+-----+-----+---+-+ * | | | | | | | * | | | | | | | * B +--+--+-----+-----+---+-+ * | | | | | | | * b | +--+-----+-----+---+ | * +-----+-----+-----+-----+ * * * Interpolation: * * l r * +-----+-----+-----+-----+ * | | | | | * | | | | | * +-----+-----+-----+-----+ * t | | +-+--+ | | * | | | | | | | * +-----+---+-+--+--+-----+ * b | | +-+--+ | | * | | | | | * +-----+-----+-----+-----+ * | | | | | * | | | | | * +-----+-----+-----+-----+ * * Same again, just no completely covered pixels. */ var L = (int) l; if (L != l) L++; var R = (int) r; var T = (int) t; if (T != t) T++; var B = (int) b; var red = 0.0; var green = 0.0; var blue = 0.0; /* Target can fit inside one source pixel * +-----+ * | | * | +--+| +-----+-----+ +-----+ +-----+ +-----+ * +-+--++ or | +-++ | or | +-+ | or | +--+| or | +--+ * | +--+| | +-++ | | +-+ | | | || | | | * | | +-----+-----+ +-----+ +-+--++ +--+--+ * +-----+ */ if ((r - l <= 1.0 && (int)r == (int)l) || (b - t <= 1.0 && (int)b == (int)t)) { /* Inside */ if ((int)l == (int)r || (int)t == (int)b) { uchar p[3]; get_pixel (page, (int)l, (int)t, p); output[offset] = p[0]; output[offset+1] = p[1]; output[offset+2] = p[2]; return; } /* Stradling horizontal edge */ if (L > R) { uchar p[3]; get_pixel (page, R, T-1, p); red += p[0] * (r-l)*(T-t); green += p[1] * (r-l)*(T-t); blue += p[2] * (r-l)*(T-t); for (var y = T; y < B; y++) { get_pixel (page, R, y, p); red += p[0] * (r-l); green += p[1] * (r-l); blue += p[2] * (r-l); } get_pixel (page, R, B, p); red += p[0] * (r-l)*(b-B); green += p[1] * (r-l)*(b-B); blue += p[2] * (r-l)*(b-B); } /* Stradling vertical edge */ else { uchar p[3]; get_pixel (page, L - 1, B, p); red += p[0] * (b-t)*(L-l); green += p[1] * (b-t)*(L-l); blue += p[2] * (b-t)*(L-l); for (var x = L; x < R; x++) { get_pixel (page, x, B, p); red += p[0] * (b-t); green += p[1] * (b-t); blue += p[2] * (b-t); } get_pixel (page, R, B, p); red += p[0] * (b-t)*(r-R); green += p[1] * (b-t)*(r-R); blue += p[2] * (b-t)*(r-R); } var scale = 1.0 / ((r - l) * (b - t)); output[offset] = (uchar)(red * scale + 0.5); output[offset+1] = (uchar)(green * scale + 0.5); output[offset+2] = (uchar)(blue * scale + 0.5); return; } /* Add the middle pixels */ for (var x = L; x < R; x++) { for (var y = T; y < B; y++) { uchar p[3]; get_pixel (page, x, y, p); red += p[0]; green += p[1]; blue += p[2]; } } /* Add the weighted top and bottom pixels */ for (var x = L; x < R; x++) { if (t != T) { uchar p[3]; get_pixel (page, x, T - 1, p); red += p[0] * (T - t); green += p[1] * (T - t); blue += p[2] * (T - t); } if (b != B) { uchar p[3]; get_pixel (page, x, B, p); red += p[0] * (b - B); green += p[1] * (b - B); blue += p[2] * (b - B); } } /* Add the left and right pixels */ for (var y = T; y < B; y++) { if (l != L) { uchar p[3]; get_pixel (page, L - 1, y, p); red += p[0] * (L - l); green += p[1] * (L - l); blue += p[2] * (L - l); } if (r != R) { uchar p[3]; get_pixel (page, R, y, p); red += p[0] * (r - R); green += p[1] * (r - R); blue += p[2] * (r - R); } } /* Add the corner pixels */ if (l != L && t != T) { uchar p[3]; get_pixel (page, L - 1, T - 1, p); red += p[0] * (L - l)*(T - t); green += p[1] * (L - l)*(T - t); blue += p[2] * (L - l)*(T - t); } if (r != R && t != T) { uchar p[3]; get_pixel (page, R, T - 1, p); red += p[0] * (r - R)*(T - t); green += p[1] * (r - R)*(T - t); blue += p[2] * (r - R)*(T - t); } if (r != R && b != B) { uchar p[3]; get_pixel (page, R, B, p); red += p[0] * (r - R)*(b - B); green += p[1] * (r - R)*(b - B); blue += p[2] * (r - R)*(b - B); } if (l != L && b != B) { uchar p[3]; get_pixel (page, L - 1, B, p); red += p[0] * (L - l)*(b - B); green += p[1] * (L - l)*(b - B); blue += p[2] * (L - l)*(b - B); } /* Scale pixel values and clamp in range [0, 255] */ var scale = 1.0 / ((r - l) * (b - t)); output[offset] = (uchar)(red * scale + 0.5); output[offset+1] = (uchar)(green * scale + 0.5); output[offset+2] = (uchar)(blue * scale + 0.5); } private void update_preview (Page page, ref Gdk.Pixbuf? output_image, int output_width, int output_height, ScanDirection scan_direction, int old_scan_line, int scan_line) { var input_width = page.width; var input_height = page.height; /* Create new image if one does not exist or has changed size */ int L, R, T, B; if (output_image == null || output_image.width != output_width || output_image.height != output_height) { output_image = new Gdk.Pixbuf (Gdk.Colorspace.RGB, false, 8, output_width, output_height); /* Update entire image */ L = 0; R = output_width - 1; T = 0; B = output_height - 1; } /* Otherwise only update changed area */ else { switch (scan_direction) { case ScanDirection.TOP_TO_BOTTOM: L = 0; R = output_width - 1; T = (int)((double)old_scan_line * output_height / input_height); B = (int)((double)scan_line * output_height / input_height + 0.5); break; case ScanDirection.LEFT_TO_RIGHT: L = (int)((double)old_scan_line * output_width / input_width); R = (int)((double)scan_line * output_width / input_width + 0.5); T = 0; B = output_height - 1; break; case ScanDirection.BOTTOM_TO_TOP: L = 0; R = output_width - 1; T = (int)((double)(input_height - scan_line) * output_height / input_height); B = (int)((double)(input_height - old_scan_line) * output_height / input_height + 0.5); break; case ScanDirection.RIGHT_TO_LEFT: L = (int)((double)(input_width - scan_line) * output_width / input_width); R = (int)((double)(input_width - old_scan_line) * output_width / input_width + 0.5); T = 0; B = output_height - 1; break; default: L = R = B = T = 0; break; } } /* FIXME: There's an off by one error in there somewhere... */ if (R >= output_width) R = output_width - 1; if (B >= output_height) B = output_height - 1; return_if_fail (L >= 0); return_if_fail (R < output_width); return_if_fail (T >= 0); return_if_fail (B < output_height); return_if_fail (output_image != null); unowned uchar[] output = output_image.get_pixels (); var output_rowstride = output_image.rowstride; var output_n_channels = output_image.n_channels; if (!page.has_data) { for (var x = L; x <= R; x++) for (var y = T; y <= B; y++) { var o = output_rowstride * y + x * output_n_channels; output[o] = output[o+1] = output[o+2] = 0xFF; } return; } /* Update changed area */ for (var x = L; x <= R; x++) { var l = (double)x * input_width / output_width; var r = (double)(x + 1) * input_width / output_width; for (var y = T; y <= B; y++) { var t = (double)y * input_height / output_height; var b = (double)(y + 1) * input_height / output_height; set_pixel (page, l, r, t, b, output, output_rowstride * y + x * output_n_channels); } } } private int get_preview_width () { return width_ - (border_width + ruler_width) * 2; } private int get_preview_height () { return height_ - (border_width + ruler_width) * 2; } private void update_page_view () { if (!update_image) return; var old_scan_line = scan_line; var scan_line = page.scan_line; /* Delete old image if scan direction changed */ var left_steps = scan_direction - page.scan_direction; if (left_steps != 0 && image != null) image = null; scan_direction = page.scan_direction; update_preview (page, ref image, get_preview_width (), get_preview_height (), page.scan_direction, old_scan_line, scan_line); update_image = false; this.scan_line = scan_line; } private int page_to_screen_x (int x) { return (int) ((double)x * get_preview_width () / page.width + 0.5); } private int page_to_screen_y (int y) { return (int) ((double)y * get_preview_height () / page.height + 0.5); } private int screen_to_page_x (int x) { return (int) ((double)x * page.width / get_preview_width () + 0.5); } private int screen_to_page_y (int y) { return (int) ((double)y * page.height / get_preview_height () + 0.5); } private CropLocation get_crop_location (int x, int y) { if (!page.has_crop) return CropLocation.NONE; var cx = page.crop_x; var cy = page.crop_y; var cw = page.crop_width; var ch = page.crop_height; var dx = page_to_screen_x (cx) + border_width + ruler_width; var dy = page_to_screen_y (cy) + border_width + ruler_width; var dw = page_to_screen_x (cw) + border_width + ruler_width; var dh = page_to_screen_y (ch) + border_width + ruler_width; var ix = x - dx; var iy = y - dy; if (ix < 0 || ix > dw || iy < 0 || iy > dh) return CropLocation.NONE; /* Can't resize named crops */ var name = page.crop_name; if (name != null) return CropLocation.MIDDLE; /* Adjust borders so can select */ int crop_border = 20; if (dw < crop_border * 3) crop_border = dw / 3; if (dh < crop_border * 3) crop_border = dh / 3; /* Top left */ if (ix < crop_border && iy < crop_border) return CropLocation.TOP_LEFT; /* Top right */ if (ix > dw - crop_border && iy < crop_border) return CropLocation.TOP_RIGHT; /* Bottom left */ if (ix < crop_border && iy > dh - crop_border) return CropLocation.BOTTOM_LEFT; /* Bottom right */ if (ix > dw - crop_border && iy > dh - crop_border) return CropLocation.BOTTOM_RIGHT; /* Left */ if (ix < crop_border) return CropLocation.LEFT; /* Right */ if (ix > dw - crop_border) return CropLocation.RIGHT; /* Top */ if (iy < crop_border) return CropLocation.TOP; /* Bottom */ if (iy > dh - crop_border) return CropLocation.BOTTOM; /* In the middle */ return CropLocation.MIDDLE; } public void button_press (int x, int y) { /* See if selecting crop */ var location = get_crop_location (x, y); if (location != CropLocation.NONE) { crop_location = location; selected_crop_px = x; selected_crop_py = y; selected_crop_x = page.crop_x; selected_crop_y = page.crop_y; selected_crop_w = page.crop_width; selected_crop_h = page.crop_height; } } public void motion (int x, int y) { var location = get_crop_location (x, y); Gdk.CursorType cursor; switch (location) { case CropLocation.MIDDLE: cursor = Gdk.CursorType.HAND1; break; case CropLocation.TOP: cursor = Gdk.CursorType.TOP_SIDE; break; case CropLocation.BOTTOM: cursor = Gdk.CursorType.BOTTOM_SIDE; break; case CropLocation.LEFT: cursor = Gdk.CursorType.LEFT_SIDE; break; case CropLocation.RIGHT: cursor = Gdk.CursorType.RIGHT_SIDE; break; case CropLocation.TOP_LEFT: cursor = Gdk.CursorType.TOP_LEFT_CORNER; break; case CropLocation.TOP_RIGHT: cursor = Gdk.CursorType.TOP_RIGHT_CORNER; break; case CropLocation.BOTTOM_LEFT: cursor = Gdk.CursorType.BOTTOM_LEFT_CORNER; break; case CropLocation.BOTTOM_RIGHT: cursor = Gdk.CursorType.BOTTOM_RIGHT_CORNER; break; default: cursor = Gdk.CursorType.ARROW; break; } if (crop_location == CropLocation.NONE) { this.cursor = cursor; return; } /* Move the crop */ var pw = page.width; var ph = page.height; var cw = page.crop_width; var ch = page.crop_height; var dx = screen_to_page_x (x - (int) selected_crop_px); var dy = screen_to_page_y (y - (int) selected_crop_py); var new_x = selected_crop_x; var new_y = selected_crop_y; var new_w = selected_crop_w; var new_h = selected_crop_h; /* Limit motion to remain within page and minimum crop size */ var min_size = screen_to_page_x (15); if (crop_location == CropLocation.TOP_LEFT || crop_location == CropLocation.LEFT || crop_location == CropLocation.BOTTOM_LEFT) { if (dx > new_w - min_size) dx = new_w - min_size; if (new_x + dx < 0) dx = -new_x; } if (crop_location == CropLocation.TOP_LEFT || crop_location == CropLocation.TOP || crop_location == CropLocation.TOP_RIGHT) { if (dy > new_h - min_size) dy = new_h - min_size; if (new_y + dy < 0) dy = -new_y; } if (crop_location == CropLocation.TOP_RIGHT || crop_location == CropLocation.RIGHT || crop_location == CropLocation.BOTTOM_RIGHT) { if (dx < min_size - new_w) dx = min_size - new_w; if (new_x + new_w + dx > pw) dx = pw - new_x - new_w; } if (crop_location == CropLocation.BOTTOM_LEFT || crop_location == CropLocation.BOTTOM || crop_location == CropLocation.BOTTOM_RIGHT) { if (dy < min_size - new_h) dy = min_size - new_h; if (new_y + new_h + dy > ph) dy = ph - new_y - new_h; } if (crop_location == CropLocation.MIDDLE) { if (new_x + dx + new_w > pw) dx = pw - new_x - new_w; if (new_x + dx < 0) dx = -new_x; if (new_y + dy + new_h > ph) dy = ph - new_y - new_h; if (new_y + dy < 0) dy = -new_y; } /* Move crop */ if (crop_location == CropLocation.MIDDLE) { new_x += dx; new_y += dy; } if (crop_location == CropLocation.TOP_LEFT || crop_location == CropLocation.LEFT || crop_location == CropLocation.BOTTOM_LEFT) { new_x += dx; new_w -= dx; } if (crop_location == CropLocation.TOP_LEFT || crop_location == CropLocation.TOP || crop_location == CropLocation.TOP_RIGHT) { new_y += dy; new_h -= dy; } if (crop_location == CropLocation.TOP_RIGHT || crop_location == CropLocation.RIGHT || crop_location == CropLocation.BOTTOM_RIGHT) new_w += dx; if (crop_location == CropLocation.BOTTOM_LEFT || crop_location == CropLocation.BOTTOM || crop_location == CropLocation.BOTTOM_RIGHT) new_h += dy; page.move_crop (new_x, new_y); /* If reshaped crop, must be a custom crop */ if (new_w != cw || new_h != ch) page.set_custom_crop (new_w, new_h); } public void button_release (int x, int y) { /* Complete crop */ crop_location = CropLocation.NONE; changed (); } private bool animation_cb () { animate_segment = (animate_segment + 1) % animate_n_segments; changed (); return true; } private void update_animation () { bool animate, is_animating; animate = page.is_scanning && !page.has_data; is_animating = animate_timeout != 0; if (animate == is_animating) return; if (animate) { animate_segment = 0; if (animate_timeout == 0) animate_timeout = Timeout.add (150, animation_cb); } else { if (animate_timeout != 0) Source.remove (animate_timeout); animate_timeout = 0; } } /* It is necessary to ask the ruler color since it is themed with the GTK */ /* theme foreground color, and this class doesn't have any GTK widget */ /* available to lookup the color. */ public void render (Cairo.Context context, Gdk.RGBA ruler_color) { update_animation (); update_page_view (); var w = get_preview_width (); var h = get_preview_height (); context.set_line_width (1); context.translate (x_offset, y_offset); /* Draw image */ context.translate (border_width + ruler_width, border_width + ruler_width); Gdk.cairo_set_source_pixbuf (context, image, 0, 0); context.paint (); /* Draw page border */ Gdk.cairo_set_source_rgba (context, ruler_color); context.set_line_width (border_width); context.rectangle (0, 0.0, w, h); context.stroke (); /* Draw horizontal ruler */ context.set_line_width (1); var ruler_tick = 0; var line = 0.0; var big_ruler_tick = 5; while (ruler_tick <= page.width) { line = page_to_screen_x (ruler_tick) + 0.5; if (big_ruler_tick == 5) { context.move_to (line, 0); context.line_to (line, -ruler_width); context.move_to (line, h); context.line_to (line, h + ruler_width); big_ruler_tick = 0; } else { context.move_to (line, -2); context.line_to (line, -5); context.move_to (line, h + 2); context.line_to (line, h + 5); } ruler_tick = ruler_tick + page.dpi/5; big_ruler_tick = big_ruler_tick + 1; } context.stroke (); /* Draw vertical ruler */ ruler_tick = 0; line = 0.0; big_ruler_tick = 5; while (ruler_tick <= page.height) { line = page_to_screen_y (ruler_tick) + 0.5; if (big_ruler_tick == 5) { context.move_to (0, line); context.line_to (-ruler_width, line); context.move_to (w, line); context.line_to (w + ruler_width, line); big_ruler_tick = 0; } else { context.move_to (-2, line); context.line_to (-5, line); context.move_to (w + 2, line); context.line_to (w + 5, line); } ruler_tick = ruler_tick + page.dpi/5; big_ruler_tick = big_ruler_tick + 1; } context.stroke (); /* Draw scan line */ if (page.is_scanning && page.scan_line > 0) { var scan_line = page.scan_line; double s; double x1, y1, x2, y2; switch (page.scan_direction) { case ScanDirection.TOP_TO_BOTTOM: s = page_to_screen_y (scan_line); x1 = 0; y1 = s + 0.5; x2 = w; y2 = s + 0.5; break; case ScanDirection.BOTTOM_TO_TOP: s = page_to_screen_y (scan_line); x1 = 0; y1 = h - s + 0.5; x2 = w; y2 = h - s + 0.5; break; case ScanDirection.LEFT_TO_RIGHT: s = page_to_screen_x (scan_line); x1 = s + 0.5; y1 = 0; x2 = s + 0.5; y2 = h; break; case ScanDirection.RIGHT_TO_LEFT: s = page_to_screen_x (scan_line); x1 = w - s + 0.5; y1 = 0; x2 = w - s + 0.5; y2 = h; break; default: x1 = y1 = x2 = y2 = 0; break; } context.move_to (x1, y1); context.line_to (x2, y2); context.set_source_rgb (1.0, 0.0, 0.0); context.stroke (); } /* Draw crop */ if (page.has_crop) { var x = page.crop_x; var y = page.crop_y; var crop_width = page.crop_width; var crop_height = page.crop_height; var dx = page_to_screen_x (x); var dy = page_to_screen_y (y); var dw = page_to_screen_x (crop_width); var dh = page_to_screen_y (crop_height); /* Shade out cropped area */ context.rectangle (0, 0, w, h); context.new_sub_path (); context.rectangle (dx, dy, dw, dh); context.set_fill_rule (Cairo.FillRule.EVEN_ODD); context.set_source_rgba (0.25, 0.25, 0.25, 0.2); context.fill (); /* Show new edge */ context.set_source_rgb (1.0, 1.0, 1.0); context.move_to (-border_width, dy - 1.5); context.line_to (border_width + w, dy - 1.5); context.move_to (-border_width, dy + dh + 1.5); context.line_to (border_width + w, dy + dh + 1.5); context.stroke (); context.move_to (dx - 1.5, -border_width); context.line_to (dx - 1.5, border_width + h); context.move_to (dx + dw + 1.5, -border_width); context.line_to (dx + dw + 1.5, border_width + h); context.stroke (); context.rectangle (dx - 0.5, dy - 0.5, dw + 1, dh + 1); context.set_source_rgb (0.0, 0.0, 0.0); context.stroke (); } } public int width { get { return width_; } set { // FIXME: Automatically update when get updated image var h = (int) ((double) value * page.height / page.width); if (width_ == value && height_ == h) return; width_ = value; height_ = h; /* Regenerate image */ update_image = true; size_changed (); changed (); } } public int height { get { return height_; } set { // FIXME: Automatically update when get updated image var w = (int) ((double) value * page.width / page.height); if (width_ == w && height_ == value) return; width_ = w; height_ = value; /* Regenerate image */ update_image = true; size_changed (); changed (); } } private void page_pixels_changed_cb (Page p) { /* Regenerate image */ update_image = true; changed (); } private void page_size_changed_cb (Page p) { /* Regenerate image */ update_image = true; size_changed (); changed (); } private void page_overlay_changed_cb (Page p) { changed (); } private void scan_direction_changed_cb (Page p) { /* Regenerate image */ update_image = true; size_changed (); changed (); } }