A refactoring attempt for the Retina display issue (#94)

This is what's been done:
 - remove the old double and single buffering /w mask approach
 - modify the bitmap rendering so it's done in a offscreen
   image before subsampling
   (effect: rulers display smoothly in subsampling mode)
 - refactoring the "device pixel ratio" topic:
   Made the DPR a variable, viewport width is scaled up
   to reflect the true image size, inserted #ifdef's for Qt4.

DISCLAIMER: I don't know whether this still works - I don't
have a Retina display :-(
This commit is contained in:
Matthias Koefferlein 2018-03-19 22:22:24 +01:00 committed by Thomas Ferreira de Lima
parent a6738f5be4
commit 0a01946202
4 changed files with 126 additions and 210 deletions

View File

@ -424,6 +424,7 @@ bitmaps_to_image_rgb (const std::vector<lay::ViewOp> &view_ops_in,
const lay::LineStyles &ls,
QImage *pimage, unsigned int width, unsigned int height,
bool use_bitmap_index,
bool transparent,
QMutex *mutex)
{
unsigned int n_in = view_ops_in.size ();
@ -583,11 +584,15 @@ bitmaps_to_image_rgb (const std::vector<lay::ViewOp> &view_ops_in,
unsigned int i = 0;
for (unsigned int x = 0; x < width; x += 32, ++i) {
lay::color_t y[32] = {
fill_bits, fill_bits, fill_bits, fill_bits, fill_bits, fill_bits, fill_bits, fill_bits,
fill_bits, fill_bits, fill_bits, fill_bits, fill_bits, fill_bits, fill_bits, fill_bits,
fill_bits, fill_bits, fill_bits, fill_bits, fill_bits, fill_bits, fill_bits, fill_bits,
fill_bits, fill_bits, fill_bits, fill_bits, fill_bits, fill_bits, fill_bits, fill_bits,
lay::color_t y[32];
if (transparent) {
for (int i = 0; i < 32; ++i) {
y[i] = 0;
}
} else {
for (int i = 0; i < 32; ++i) {
y[i] = fill_bits;
}
};
lay::color_t z[32] = {
@ -603,17 +608,32 @@ bitmaps_to_image_rgb (const std::vector<lay::ViewOp> &view_ops_in,
dptr = dptr_end - nwords + i;
for (int j = masks.size () - 1; j >= 0; --j) {
uint32_t d = *dptr;
if (d != 0) {
uint32_t m = 1;
for (unsigned int k = 0; k < 32 && x + k < width; ++k, m <<= 1) {
if ((d & m) != 0) {
y [k] |= masks [j].first & z [k];
z [k] &= masks [j].second;
if (transparent) {
uint32_t m = 1;
for (unsigned int k = 0; k < 32 && x + k < width; ++k, m <<= 1) {
if ((d & m) != 0) {
y [k] |= (masks [j].first & z [k]) | fill_bits;
z [k] &= masks [j].second;
}
}
} else {
uint32_t m = 1;
for (unsigned int k = 0; k < 32 && x + k < width; ++k, m <<= 1) {
if ((d & m) != 0) {
y [k] |= masks [j].first & z [k];
z [k] &= masks [j].second;
}
}
}
}
dptr -= nwords;
}
for (unsigned int k = 0; k < 32 && x + k < width; ++k) {
@ -843,7 +863,8 @@ bitmaps_to_image (const std::vector<lay::ViewOp> &view_ops_in,
if (pimage->depth () <= 1) {
bitmaps_to_image_mono (view_ops_in, pbitmaps_in, dp, ls, pimage, width, height, use_bitmap_index, mutex);
} else {
bitmaps_to_image_rgb (view_ops_in, pbitmaps_in, dp, ls, pimage, width, height, use_bitmap_index, mutex);
bool transparent = (pimage->format () == QImage::Format_ARGB32);
bitmaps_to_image_rgb (view_ops_in, pbitmaps_in, dp, ls, pimage, width, height, use_bitmap_index, transparent, mutex);
}
}

View File

@ -27,6 +27,7 @@
#include <QPainter>
#include <QApplication>
#include <QBuffer>
#include <QWheelEvent>
#include "tlTimer.h"
#include "tlLog.h"
@ -130,19 +131,6 @@ std::string ImageCacheEntry::to_string () const
// ----------------------------------------------------------------------------
#if defined (_WIN32) || defined (Q_WS_MAC) || QT_VERSION >= 0x40600
// Windows build runs well with single buffering
// and Qt built with double buffering.
// The same is true vor MAC OS X
// In Qt 4.8 the SINGLE_BUFFERED_MASK implementation no longer
// works properly and double buffering is default anyway.
# define SINGLE_BUFFERED
#else
// X11 runs better with the SINGLE_BUFFERED_MASK scheme
// # define SINGLE_BUFFERED
# define SINGLE_BUFFERED_MASK
#endif
static void
blowup (const QImage &src, QImage &dest, unsigned int os)
{
@ -164,28 +152,46 @@ blowup (const QImage &src, QImage &dest, unsigned int os)
}
static void
subsample (const QImage &src, QImage &dest, unsigned int os)
subsample (const QImage &src, QImage &dest, unsigned int os, double g)
{
// TODO: this is probably not compatible with the endianess of SPARC ..
// TODO: this is probably not compatible with the endianess of SPARC ..
double g = 2.0; // is something 1.8 .. 2.2
// LUT's for combining the RGB channels
// forward transformation table
unsigned short lut1[256];
for (unsigned int i = 0; i < 256; ++i) {
double f = (65536 / (os * os)) - 1;
lut1[i] = (unsigned short)std::min (f, std::max (0.0, floor (0.5 + pow (i / 255.0, g) * f)));
}
// backward transformation table
unsigned char lut2[65536];
for (unsigned int i = 0; i < 65536; ++i) {
double f = os * os * ((65536 / (os * os)) - 1);
lut2[i] = (unsigned char)std::min (255.0, std::max (0.0, floor (0.5 + pow (i / f, 1.0 / g) * 255.0)));
}
// LUT's for alpha channel
// forward transformation table
unsigned short luta1[256];
for (unsigned int i = 0; i < 256; ++i) {
double f = (65536 / (os * os)) - 1;
luta1[i] = (unsigned short)std::min (f, std::max (0.0, floor (0.5 + (i / 255.0) * f)));
}
// backward transformation table
unsigned char luta2[65536];
for (unsigned int i = 0; i < 65536; ++i) {
double f = os * os * ((65536 / (os * os)) - 1);
luta2[i] = (unsigned char)std::min (255.0, std::max (0.0, floor (0.5 + (i / f) * 255.0)));
}
unsigned int ymax = dest.height ();
unsigned int xmax = dest.width ();
unsigned short *buffer = new unsigned short[xmax * 3];
unsigned short *buffer = new unsigned short[xmax * 4];
for (unsigned int y = 0; y < ymax; ++y) {
@ -199,16 +205,18 @@ subsample (const QImage &src, QImage &dest, unsigned int os)
pdest[0] = lut1[psrc[0]];
pdest[1] = lut1[psrc[1]];
pdest[2] = lut1[psrc[2]];
pdest[3] = luta1[psrc[3]];
psrc += 4;
for (unsigned int j = os; j > 1; j--) {
pdest[0] += lut1[psrc[0]];
pdest[1] += lut1[psrc[1]];
pdest[2] += lut1[psrc[2]];
pdest[3] += luta1[psrc[3]];
psrc += 4;
}
pdest += 3;
pdest += 4;
}
@ -225,10 +233,11 @@ subsample (const QImage &src, QImage &dest, unsigned int os)
pdest[0] += lut1[psrc[0]];
pdest[1] += lut1[psrc[1]];
pdest[2] += lut1[psrc[2]];
pdest[3] += luta1[psrc[3]];
psrc += 4;
}
pdest += 3;
pdest += 4;
}
@ -243,7 +252,7 @@ subsample (const QImage &src, QImage &dest, unsigned int os)
*pdest++ = lut2[*psrc++];
*pdest++ = lut2[*psrc++];
*pdest++ = lut2[*psrc++];
*pdest++ = 0xff;
*pdest++ = luta2[*psrc++];
}
}
@ -253,64 +262,6 @@ subsample (const QImage &src, QImage &dest, unsigned int os)
delete[] buffer;
}
void
subsample (unsigned char *data, unsigned int width, unsigned int height, unsigned int os)
{
if (os == 1) {
return;
}
unsigned int nbytes = (width * os + 7) / 8;
unsigned char *psrc = data;
unsigned char *pdest = psrc;
for (unsigned int y = 0; y < height; ++y) {
unsigned int bdest = 0;
unsigned int bsrc = 0;
unsigned char dd = 0;
unsigned char m = 0x01;
for (unsigned int i = 0; i < nbytes; ++i) {
unsigned char *p = psrc++;
unsigned char d = *p;
for (unsigned int j = 1; j < os; ++j) {
p += nbytes;
d |= *p;
}
for (unsigned int b = 0; b < 8; ++b) {
if ((d & 1) != 0) {
dd |= m;
}
d >>= 1;
if (++bsrc == os) {
bsrc = 0;
++bdest;
m <<= 1;
if (bdest == 8) {
*pdest++ = dd;
bdest = 0;
dd = 0;
m = 0x01;
}
}
}
}
psrc += nbytes * (os - 1);
if (bdest > 0) {
*pdest++ = dd;
}
}
}
void
invert (unsigned char *data, unsigned int width, unsigned int height)
{
@ -329,6 +280,7 @@ LayoutCanvas::LayoutCanvas (QWidget *parent, lay::LayoutView *view, const char *
mp_image (0), mp_image_bg (0), mp_pixmap (0),
m_background (0), m_foreground (0), m_active (0),
m_oversampling (1),
m_dpr (1),
m_need_redraw (false),
m_redraw_clearing (false),
m_redraw_force_update (true),
@ -337,9 +289,18 @@ LayoutCanvas::LayoutCanvas (QWidget *parent, lay::LayoutView *view, const char *
m_do_end_of_drawing_dm (this, &LayoutCanvas::do_end_of_drawing),
m_image_cache_size (1)
{
#if QT_VERSION > 0x050000
m_dpr = devicePixelRatio ();
#else
m_dpr = 1;
#endif
// The gamma value used for subsampling: something between 1.8 and 2.2.
m_gamma = 2.0;
// some reasonable initializations for the size
m_viewport.set_size (100, 100);
m_viewport_l.set_size (m_viewport.width () * m_oversampling * 2, m_viewport.height () * m_oversampling * 2);
m_viewport_l.set_size (m_viewport.width () * m_oversampling, m_viewport.height () * m_oversampling);
mp_redraw_thread = new lay::RedrawThread (this, view);
@ -347,9 +308,6 @@ LayoutCanvas::LayoutCanvas (QWidget *parent, lay::LayoutView *view, const char *
set_colors (palette ().color (QPalette::Normal, QPalette::Background),
palette ().color (QPalette::Normal, QPalette::Text),
palette ().color (QPalette::Normal, QPalette::Mid));
#ifndef SINGLE_BUFFERED
setAttribute (Qt::WA_PaintOnScreen);
#endif
setAttribute (Qt::WA_NoSystemBackground);
}
@ -416,7 +374,7 @@ LayoutCanvas::set_oversampling (unsigned int os)
if (os != m_oversampling) {
m_image_cache.clear ();
m_oversampling = os;
m_viewport_l.set_size (m_viewport.width () * m_oversampling * 2, m_viewport.height () * m_oversampling * 2);
m_viewport_l.set_size (m_viewport.width () * m_oversampling, m_viewport.height () * m_oversampling);
do_redraw_all ();
}
}
@ -469,7 +427,7 @@ LayoutCanvas::prepare_drawing ()
{
if (m_need_redraw) {
BitmapViewObjectCanvas::set_size (m_viewport_l.width (), m_viewport_l.height (), 1.0 / double (m_oversampling) / 2);
BitmapViewObjectCanvas::set_size (m_viewport_l.width (), m_viewport_l.height (), 1.0 / double (m_oversampling * m_dpr));
if (! mp_image ||
(unsigned int) mp_image->width () != m_viewport_l.width () ||
@ -478,7 +436,9 @@ LayoutCanvas::prepare_drawing ()
delete mp_image;
}
mp_image = new QImage (m_viewport_l.width (), m_viewport_l.height (), QImage::Format_RGB32);
mp_image->setDevicePixelRatio(2.0);
#if QT_VERSION > 0x050000
mp_image->setDevicePixelRatio (double (m_dpr));
#endif
if (mp_pixmap) {
delete mp_pixmap;
mp_pixmap = 0;
@ -506,7 +466,7 @@ LayoutCanvas::prepare_drawing ()
++c;
}
mp_redraw_thread->commit (m_layers, m_viewport_l, 1.0 / double (m_oversampling) / 2);
mp_redraw_thread->commit (m_layers, m_viewport_l, 1.0 / double (m_oversampling * m_dpr));
if (tl::verbosity () >= 20) {
tl::info << "Restored image from cache";
@ -556,7 +516,7 @@ LayoutCanvas::prepare_drawing ()
}
if (m_redraw_clearing) {
mp_redraw_thread->start (mp_view->synchronous () ? 0 : mp_view->drawing_workers (), m_layers, m_viewport_l, 1.0 / double (m_oversampling) / 2, m_redraw_force_update);
mp_redraw_thread->start (mp_view->synchronous () ? 0 : mp_view->drawing_workers (), m_layers, m_viewport_l, 1.0 / double (m_oversampling * m_dpr), m_redraw_force_update);
} else {
mp_redraw_thread->restart (m_need_redraw_layer);
}
@ -637,11 +597,6 @@ LayoutCanvas::paintEvent (QPaintEvent *)
}
// prepare a buffer
size_t nbytes = (m_viewport_l.width () + 7) / 8 * m_viewport_l.height ();
unsigned char *p_data = new unsigned char [nbytes];
memset (p_data, 0, nbytes);
// create a base pixmap consisting of the layout with background
// and static foreground objects
@ -661,16 +616,20 @@ LayoutCanvas::paintEvent (QPaintEvent *)
if (fg_bitmaps () > 0) {
QImage full_image (*mp_image);
full_image.setDevicePixelRatio(2.0);
#if QT_VERSION > 0x050000
full_image.setDevicePixelRatio (double (m_dpr));
#endif
bitmaps_to_image (fg_view_op_vector (), fg_bitmap_vector (), dither_pattern (), line_styles (), &full_image, m_viewport_l.width (), m_viewport_l.height (), false, &m_mutex);
// render the foreground parts ..
if (m_oversampling == 1) {
*mp_pixmap = QPixmap::fromImage (full_image); // Qt 4.6.0 workaround
} else {
QImage subsampled_image (m_viewport.width () * 2, m_viewport.height () * 2, mp_image->format ());
subsampled_image.setDevicePixelRatio(2.0);
subsample (full_image, subsampled_image, m_oversampling);
QImage subsampled_image (m_viewport.width (), m_viewport.height (), mp_image->format ());
#if QT_VERSION > 0x050000
subsampled_image.setDevicePixelRatio (double (m_dpr));
#endif
subsample (full_image, subsampled_image, m_oversampling, m_gamma);
*mp_pixmap = QPixmap::fromImage (subsampled_image); // Qt 4.6.0 workaround
}
@ -680,9 +639,11 @@ LayoutCanvas::paintEvent (QPaintEvent *)
} else {
QImage subsampled_image (m_viewport.width () * 2, m_viewport.height () * 2, mp_image->format ());
subsampled_image.setDevicePixelRatio(2.0);
subsample (*mp_image, subsampled_image, m_oversampling);
QImage subsampled_image (m_viewport.width (), m_viewport.height (), mp_image->format ());
#if QT_VERSION > 0x050000
subsampled_image.setDevicePixelRatio (double (m_dpr));
#endif
subsample (*mp_image, subsampled_image, m_oversampling, m_gamma);
*mp_pixmap = QPixmap::fromImage (subsampled_image);
}
@ -692,109 +653,36 @@ LayoutCanvas::paintEvent (QPaintEvent *)
// erase any previous data
clear_fg_bitmaps ();
// render foreground content
// render dynamic foreground content
do_render (m_viewport_l, *this, false);
#if defined(SINGLE_BUFFERED)
// Single-buffering scheme - somewhat more flickering
// but less resource requirements.
// produce the pixmap first and then overdraw with dynamic content.
QPainter painter (this);
painter.drawPixmap (QPoint (0, 0), *mp_pixmap);
// dynamic bitmaps are always drawn in transparent mode ..
painter.setBackgroundMode (Qt::TransparentMode);
if (fg_bitmaps () > 0) {
// create QBitmap objects and paint them for the bitmaps
// present
for (unsigned int n = 0; n < fg_bitmaps (); ++n) {
if (fg_bitmap (n) != 0) {
memset (p_data, 0, nbytes);
bitmap_to_bitmap (fg_style (n), *fg_bitmap (n), p_data, m_viewport_l.width (), m_viewport_l.height (), dither_pattern (), line_styles ());
subsample (p_data, m_viewport.width (), m_viewport.height (), m_oversampling * 2);
QBitmap bitmap = QBitmap::fromData (QSize (m_viewport.width (), m_viewport.height ()), p_data);
QPen pen (QRgb (fg_style (n).ormask ()));
//pen.setWidthF(1.5);
painter.setPen (pen);
painter.drawPixmap (0, 0, bitmap);
}
}
#elif defined(SINGLE_BUFFERED_MASK)
// Single-buffering scheme with mask - somewhat more flickering
// but less resource requirements.
// combine the bitmaps into a mask
for (unsigned int n = 0; n < fg_bitmaps (); ++n) {
if (fg_bitmap (n) != 0) {
bitmap_to_bitmap (fg_style (n), *fg_bitmap (n), p_data, m_viewport_l.width (), m_viewport_l.height (), dither_pattern ());
}
}
// invert and subsample the mask
subsample (p_data, m_viewport.width (), m_viewport.height (), m_oversampling * 2);
invert (p_data, m_viewport.width (), m_viewport.height ());
// create the mask
QBitmap mask = QBitmap::fromData (QSize (m_viewport.width (), m_viewport.height ()), p_data);
mp_pixmap->setMask (mask);
// produce the pixmap with the mask
QPainter painter (this);
painter.drawPixmap (QPoint (0, 0), *mp_pixmap);
// dynamic bitmaps are always drawn in transparent mode ..
painter.setBackgroundMode (Qt::TransparentMode);
// create QBitmap objects and paint them for the bitmaps
// present
for (unsigned int n = 0; n < fg_bitmaps (); ++n) {
if (fg_bitmap (n) != 0) {
memset (p_data, 0, nbytes);
bitmap_to_bitmap (fg_style (n), *fg_bitmap (n), p_data, m_viewport_l.width (), m_viewport_l.height (), dither_pattern ());
subsample (p_data, m_viewport.width (), m_viewport.height (), m_oversampling * 2);
QBitmap bitmap = QBitmap::fromData (QSize (m_viewport.width (), m_viewport.height ()), p_data);
painter.setPen (QRgb (fg_style (n).ormask ()));
painter.drawPixmap (0, 0, bitmap);
}
}
#else
// Double-buffering scheme - somewhat less flickering but
// only efficient if the QImage has the precise size and
// at the cost of twice the X resource requirements.
// create an intermediate pixmap
QPixmap px (*mp_pixmap);
// dynamic bitmaps are always drawn in transparent mode ..
QPainter painter (&px);
painter.setBackgroundMode (Qt::TransparentMode);
// create QBitmap objects and paint them for the bitmaps
// present
for (unsigned int n = 0; n < fg_bitmaps (); ++n) {
if (fg_bitmap (n) != 0) {
memset (p_data, 0, nbytes);
bitmap_to_bitmap (fg_style (n), *fg_bitmap (n), p_data, m_viewport.width (), m_viewport.height (), dither_pattern ());
QBitmap bitmap = QBitmap::fromData (QSize (m_viewport.width (), m_viewport.height ()), p_data);
painter.setPen (QRgb (fg_style (n).ormask ()));
painter.drawPixmap (0, 0, bitmap);
}
}
// produce the combined pixmap on the window
QPainter screen_painter (this);
screen_painter.drawPixmap (QPoint (0, 0), px);
QImage full_image (mp_image->size ().width (), mp_image->size ().height (), QImage::Format_ARGB32);
full_image.fill (0);
#if QT_VERSION > 0x050000
full_image.setDevicePixelRatio (double (m_dpr));
#endif
bitmaps_to_image (fg_view_op_vector (), fg_bitmap_vector (), dither_pattern (), line_styles (), &full_image, m_viewport_l.width (), m_viewport_l.height (), false, &m_mutex);
// delete buffer
delete [] p_data;
// render the foreground parts ..
if (m_oversampling == 1) {
painter.drawPixmap (QPoint (0, 0), QPixmap::fromImage (full_image));
} else {
QImage subsampled_image (m_viewport.width (), m_viewport.height (), QImage::Format_ARGB32);
#if QT_VERSION > 0x050000
subsampled_image.setDevicePixelRatio (double (m_dpr));
#endif
subsample (full_image, subsampled_image, m_oversampling, m_gamma);
painter.drawPixmap (QPoint (0, 0), QPixmap::fromImage (subsampled_image));
}
}
// erase dynamic bitmaps
clear_fg_bitmaps ();
@ -815,6 +703,9 @@ public:
: BitmapViewObjectCanvas (width_l, height_l, resolution),
m_bg (bg), m_fg (fg), m_ac (ac), mp_image (img)
{
// TODO: Good choice?
m_gamma = 2.0;
if (img->width () != int (width_l) || img->height () != int (height_l)) {
mp_image_l = new QImage (width_l, height_l, img->format ());
mp_image_l->fill (bg.rgb ());
@ -859,7 +750,7 @@ public:
unsigned int os = mp_image_l->width () / width;
blowup (*mp_image, *mp_image_l, os);
bitmaps_to_image (fg_view_op_vector (), fg_bitmap_vector (), dp, ls, mp_image_l, mp_image_l->width (), mp_image_l->height (), false, 0);
subsample (*mp_image_l, *mp_image, os);
subsample (*mp_image_l, *mp_image, os, m_gamma);
} else {
bitmaps_to_image (fg_view_op_vector (), fg_bitmap_vector (), dp, ls, mp_image, width, height, false, 0);
}
@ -870,7 +761,7 @@ public:
{
if (mp_image_l && mp_image->width () > 0) {
unsigned int os = mp_image_l->width () / mp_image->width ();
subsample (*mp_image_l, *mp_image, os);
subsample (*mp_image_l, *mp_image, os, m_gamma);
}
}
@ -878,6 +769,7 @@ private:
QColor m_bg, m_fg, m_ac;
QImage *mp_image;
QImage *mp_image_l;
double m_gamma;
};
QImage
@ -896,7 +788,7 @@ LayoutCanvas::image_with_options (unsigned int width, unsigned int height, int l
linewidth = 1;
}
if (resolution <= 0.0) {
resolution = 2.0 / oversampling;
resolution = 1.0 / oversampling;
}
if (background == QColor ()) {
background = background_color ();
@ -986,7 +878,7 @@ LayoutCanvas::screenshot ()
QImage img (m_viewport.width (), m_viewport.height (), QImage::Format_RGB32);
img.fill (m_background);
DetachedViewObjectCanvas vo_canvas (background_color (), foreground_color (), active_color (), m_viewport_l.width (), m_viewport_l.height (), 1.0 / double (m_oversampling) / 2, &img);
DetachedViewObjectCanvas vo_canvas (background_color (), foreground_color (), active_color (), m_viewport_l.width (), m_viewport_l.height (), 1.0 / double (m_oversampling * m_dpr), &img);
// and paint the background objects. It uses "img" to paint on.
do_render_bg (m_viewport_l, vo_canvas);
@ -1014,8 +906,8 @@ LayoutCanvas::resizeEvent (QResizeEvent *)
m_image_cache.clear ();
// set the viewport to the new size
m_viewport.set_size (width (), height ());
m_viewport_l.set_size (width () * m_oversampling * 2, height () * m_oversampling * 2);
m_viewport.set_size (width () * m_dpr, height () * m_dpr);
m_viewport_l.set_size (width () * m_oversampling * m_dpr, height () * m_oversampling * m_dpr);
mouse_event_trans (m_viewport.trans ());
do_redraw_all (true);
viewport_changed_event ();

View File

@ -367,6 +367,8 @@ private:
lay::DitherPattern m_dither_pattern;
lay::LineStyles m_line_styles;
unsigned int m_oversampling;
unsigned int m_dpr;
double m_gamma;
bool m_need_redraw;
bool m_redraw_clearing;

View File

@ -59,7 +59,8 @@ void
RubberBox::render (const Viewport &vp, ViewObjectCanvas &canvas)
{
lay::Renderer &r = canvas.renderer ();
lay::CanvasPlane *plane = canvas.plane (lay::ViewOp (m_color, lay::ViewOp::Copy, 0, m_stipple, 0));
int lw = int (0.5 + 1.0 / r.resolution ());
lay::CanvasPlane *plane = canvas.plane (lay::ViewOp (m_color, lay::ViewOp::Copy, 0, m_stipple, 0, lay::ViewOp::Rect, lw));
if (plane) {
r.draw (vp.trans () * db::DBox (m_p1, m_p2), 0, plane, 0, 0);
}