mirror of https://github.com/KLayout/klayout.git
1230 lines
33 KiB
C++
1230 lines
33 KiB
C++
|
|
/*
|
|
|
|
KLayout Layout Viewer
|
|
Copyright (C) 2006-2022 Matthias Koefferlein
|
|
|
|
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 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program 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 General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
|
|
*/
|
|
|
|
#if defined(HAVE_QT)
|
|
# include <QEvent>
|
|
# include <QPainter>
|
|
# include <QApplication>
|
|
# include <QWheelEvent>
|
|
#endif
|
|
|
|
#include "tlTimer.h"
|
|
#include "tlLog.h"
|
|
#include "tlAssert.h"
|
|
#include "layLayoutCanvas.h"
|
|
#include "layRedrawThread.h"
|
|
#include "layLayoutViewBase.h"
|
|
#include "layMarker.h"
|
|
#if defined(HAVE_QT)
|
|
# include "gtf.h"
|
|
#endif
|
|
|
|
#include "layBitmapsToImage.h"
|
|
|
|
#include <sstream>
|
|
#include <algorithm>
|
|
|
|
namespace lay
|
|
{
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
/**
|
|
* @brief Returns a value indicating whether the needed entry can make use of the one in the cache
|
|
*/
|
|
static bool applies (const lay::RedrawLayerInfo &in_cache, const lay::RedrawLayerInfo &needed)
|
|
{
|
|
if (needed.visible && !in_cache.visible) {
|
|
return false;
|
|
}
|
|
|
|
if (needed.cell_frame != in_cache.cell_frame ||
|
|
needed.xfill != in_cache.xfill ||
|
|
needed.layer_index != in_cache.layer_index ||
|
|
needed.cellview_index != in_cache.cellview_index ||
|
|
needed.hier_levels != in_cache.hier_levels ||
|
|
needed.prop_sel != in_cache.prop_sel ||
|
|
needed.inverse_prop_sel != in_cache.inverse_prop_sel) {
|
|
return false;
|
|
}
|
|
|
|
if (needed.trans.size () != in_cache.trans.size ()) {
|
|
return false;
|
|
}
|
|
|
|
for (size_t i = 0; i < needed.trans.size (); ++i) {
|
|
if (! needed.trans[i].equal (in_cache.trans[i])) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
ImageCacheEntry::ImageCacheEntry (const lay::Viewport &vp, const std::vector<lay::RedrawLayerInfo> &layers, bool precious)
|
|
: m_opened (true), m_trans (vp.trans ()), m_layers (layers), m_width (vp.width ()), m_height (vp.height ()), m_precious (precious)
|
|
{
|
|
// .. nothing yet ..
|
|
}
|
|
|
|
bool ImageCacheEntry::equals (const lay::Viewport &vp, const std::vector<lay::RedrawLayerInfo> &layers) const
|
|
{
|
|
if (!m_trans.equal (vp.trans ()) || m_width != vp.width () || m_height != vp.height ()) {
|
|
return false;
|
|
}
|
|
|
|
if (m_layers.size () != layers.size ()) {
|
|
return false;
|
|
}
|
|
|
|
for (size_t i = 0; i < m_layers.size (); ++i) {
|
|
if (! applies (m_layers [i], layers [i])) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void ImageCacheEntry::close (const BitmapCanvasData &data)
|
|
{
|
|
m_data = data;
|
|
m_opened = false;
|
|
}
|
|
|
|
void ImageCacheEntry::swap (ImageCacheEntry &other)
|
|
{
|
|
std::swap (m_opened, other.m_opened);
|
|
std::swap (m_layers, other.m_layers);
|
|
std::swap (m_trans, other.m_trans);
|
|
std::swap (m_width, other.m_width);
|
|
std::swap (m_height, other.m_height);
|
|
std::swap (m_precious, other.m_precious);
|
|
m_data.swap (other.m_data);
|
|
}
|
|
|
|
std::string ImageCacheEntry::to_string () const
|
|
{
|
|
return std::string (m_opened ? "(" : "") + std::string (m_precious ? "*" : " ") +
|
|
tl::to_string (m_width) + "x" + tl::to_string (m_height) + " " +
|
|
m_trans.to_string () + std::string (m_opened ? ")" : "");
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
static void
|
|
blowup (const tl::PixelBuffer &src, tl::PixelBuffer &dest, unsigned int os)
|
|
{
|
|
unsigned int ymax = src.height ();
|
|
unsigned int xmax = src.width ();
|
|
|
|
for (unsigned int y = 0; y < ymax; ++y) {
|
|
for (unsigned int i = 0; i < os; ++i) {
|
|
const uint32_t *psrc = (const uint32_t *) src.scan_line (y);
|
|
uint32_t *pdest = (uint32_t *) dest.scan_line (y * os + i);
|
|
for (unsigned int x = 0; x < xmax; ++x) {
|
|
for (unsigned int j = 0; j < os; ++j) {
|
|
*pdest++ = *psrc;
|
|
}
|
|
++psrc;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
subsample (const tl::PixelBuffer &src, tl::PixelBuffer &dest, unsigned int os, double g)
|
|
{
|
|
// TODO: this is probably not compatible with the endianess of SPARC ..
|
|
|
|
// 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 * 4];
|
|
|
|
for (unsigned int y = 0; y < ymax; ++y) {
|
|
|
|
{
|
|
|
|
const unsigned char *psrc = (const unsigned char *) src.scan_line (y * os);
|
|
unsigned short *pdest = buffer;
|
|
|
|
for (unsigned int x = 0; x < xmax; ++x) {
|
|
|
|
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 += 4;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (unsigned int i = 1; i < os; ++i) {
|
|
|
|
const unsigned char *psrc = (const unsigned char *) src.scan_line (y * os + i);
|
|
unsigned short *pdest = buffer;
|
|
|
|
for (unsigned int x = 0; x < xmax; ++x) {
|
|
|
|
for (unsigned int j = os; j > 0; j--) {
|
|
pdest[0] += lut1[psrc[0]];
|
|
pdest[1] += lut1[psrc[1]];
|
|
pdest[2] += lut1[psrc[2]];
|
|
pdest[3] += luta1[psrc[3]];
|
|
psrc += 4;
|
|
}
|
|
|
|
pdest += 4;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
{
|
|
|
|
unsigned char *pdest = (unsigned char *) dest.scan_line (y);
|
|
const unsigned short *psrc = buffer;
|
|
|
|
for (unsigned int x = 0; x < xmax; ++x) {
|
|
*pdest++ = lut2[*psrc++];
|
|
*pdest++ = lut2[*psrc++];
|
|
*pdest++ = lut2[*psrc++];
|
|
*pdest++ = luta2[*psrc++];
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
delete[] buffer;
|
|
}
|
|
|
|
void
|
|
invert (unsigned char *data, unsigned int width, unsigned int height)
|
|
{
|
|
unsigned int nbytes = (width + 7) / 8;
|
|
unsigned char *psrc = data;
|
|
for (unsigned int y = 0; y < height; ++y) {
|
|
for (unsigned int i = 0; i < nbytes; ++i) {
|
|
*psrc++ ^= 0xff;
|
|
}
|
|
}
|
|
}
|
|
|
|
LayoutCanvas::LayoutCanvas (lay::LayoutViewBase *view)
|
|
: lay::ViewObjectUI (),
|
|
mp_view (view),
|
|
mp_image (0), mp_image_bg (0),
|
|
mp_image_fg (0),
|
|
m_background (0), m_foreground (0), m_active (0),
|
|
m_oversampling (1),
|
|
m_need_redraw (false),
|
|
m_redraw_clearing (false),
|
|
m_redraw_force_update (true),
|
|
m_update_image (true),
|
|
m_drawing_finished (false),
|
|
m_do_update_image_dm (this, &LayoutCanvas::do_update_image),
|
|
m_do_end_of_drawing_dm (this, &LayoutCanvas::do_end_of_drawing),
|
|
m_image_cache_size (1)
|
|
{
|
|
// 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, m_viewport.height () * m_oversampling);
|
|
|
|
mp_redraw_thread = new lay::RedrawThread (this, view);
|
|
|
|
tl::Color bg (0xffffffff), fg (0xff000000), active (0xffc0c0c0);
|
|
set_colors (bg, fg, active);
|
|
}
|
|
|
|
LayoutCanvas::~LayoutCanvas ()
|
|
{
|
|
// Detach all listeners so we don't trigger events in the destructor
|
|
viewport_changed_event.clear ();
|
|
|
|
if (mp_image) {
|
|
delete mp_image;
|
|
mp_image = 0;
|
|
}
|
|
if (mp_image_bg) {
|
|
delete mp_image_bg;
|
|
mp_image_bg = 0;
|
|
}
|
|
if (mp_image_fg) {
|
|
delete mp_image_fg;
|
|
mp_image_fg = 0;
|
|
}
|
|
if (mp_redraw_thread) {
|
|
delete mp_redraw_thread;
|
|
mp_redraw_thread = 0;
|
|
}
|
|
|
|
clear_fg_bitmaps ();
|
|
}
|
|
|
|
#if defined(HAVE_QT) && QT_VERSION >= 0x050000
|
|
double
|
|
LayoutCanvas::dpr ()
|
|
{
|
|
return widget () ? widget ()->devicePixelRatio () : 1.0;
|
|
}
|
|
#else
|
|
double
|
|
LayoutCanvas::dpr ()
|
|
{
|
|
return 1.0;
|
|
}
|
|
#endif
|
|
|
|
#if defined(HAVE_QT)
|
|
void
|
|
LayoutCanvas::init_ui (QWidget *parent)
|
|
{
|
|
lay::ViewObjectUI::init_ui (parent);
|
|
|
|
if (widget ()) {
|
|
|
|
widget ()->setObjectName (QString::fromUtf8 ("canvas"));
|
|
widget ()->setBackgroundRole (QPalette::NoRole);
|
|
|
|
tl::Color bg = tl::Color (widget ()->palette ().color (QPalette::Normal, QPalette::Window).rgb ());
|
|
tl::Color fg = tl::Color (widget ()->palette ().color (QPalette::Normal, QPalette::Text).rgb ());
|
|
tl::Color active = tl::Color (widget ()->palette ().color (QPalette::Normal, QPalette::Mid).rgb ());
|
|
set_colors (bg, fg, active);
|
|
|
|
widget ()->setAttribute (Qt::WA_NoSystemBackground);
|
|
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void
|
|
LayoutCanvas::key_event (unsigned int key, unsigned int buttons)
|
|
{
|
|
if (! (buttons & lay::ShiftButton)) {
|
|
if (int (key) == lay::KeyDown) {
|
|
down_arrow_key_pressed ();
|
|
} else if (int (key) == lay::KeyUp) {
|
|
up_arrow_key_pressed ();
|
|
} else if (int (key) == lay::KeyLeft) {
|
|
left_arrow_key_pressed ();
|
|
} else if (int (key) == lay::KeyRight) {
|
|
right_arrow_key_pressed ();
|
|
}
|
|
} else {
|
|
if (int (key) == lay::KeyDown) {
|
|
down_arrow_key_pressed_with_shift ();
|
|
} else if (int (key) == lay::KeyUp) {
|
|
up_arrow_key_pressed_with_shift ();
|
|
} else if (int (key) == lay::KeyLeft) {
|
|
left_arrow_key_pressed_with_shift ();
|
|
} else if (int (key) == lay::KeyRight) {
|
|
right_arrow_key_pressed_with_shift ();
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
LayoutCanvas::set_image_cache_size (size_t sz)
|
|
{
|
|
m_image_cache_size = sz;
|
|
}
|
|
|
|
void
|
|
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, m_viewport.height () * m_oversampling);
|
|
do_redraw_all ();
|
|
}
|
|
}
|
|
|
|
void
|
|
LayoutCanvas::set_colors (tl::Color background, tl::Color foreground, tl::Color active)
|
|
{
|
|
m_background = background.rgb ();
|
|
m_foreground = foreground.rgb ();
|
|
m_active = active.rgb ();
|
|
|
|
// force regeneration of background image ..
|
|
if (mp_image_bg) {
|
|
delete mp_image_bg;
|
|
}
|
|
mp_image_bg = 0;
|
|
|
|
update_image ();
|
|
}
|
|
|
|
void
|
|
LayoutCanvas::set_view_ops (std::vector <lay::ViewOp> &view_ops)
|
|
{
|
|
if (view_ops != m_view_ops) {
|
|
m_view_ops.swap (view_ops);
|
|
m_scaled_view_ops.clear ();
|
|
update_image ();
|
|
}
|
|
}
|
|
|
|
void
|
|
LayoutCanvas::set_dither_pattern (const lay::DitherPattern &p)
|
|
{
|
|
if (p != m_dither_pattern) {
|
|
m_dither_pattern = p;
|
|
update_image ();
|
|
}
|
|
}
|
|
|
|
void
|
|
LayoutCanvas::set_line_styles (const lay::LineStyles &s)
|
|
{
|
|
if (s != m_line_styles) {
|
|
m_line_styles = s;
|
|
update_image ();
|
|
}
|
|
}
|
|
|
|
const std::vector <lay::ViewOp> &
|
|
LayoutCanvas::scaled_view_ops (unsigned int lw)
|
|
{
|
|
if (lw <= 1) {
|
|
return m_view_ops;
|
|
}
|
|
|
|
auto cached = m_scaled_view_ops.find (lw);
|
|
if (cached != m_scaled_view_ops.end ()) {
|
|
return cached->second;
|
|
}
|
|
|
|
std::vector<lay::ViewOp> &scaled_view_ops = m_scaled_view_ops [lw];
|
|
scaled_view_ops = m_view_ops;
|
|
for (std::vector<lay::ViewOp>::iterator vo = scaled_view_ops.begin (); vo != scaled_view_ops.end (); ++vo) {
|
|
vo->width (std::min (31, vo->width () * int (lw)));
|
|
}
|
|
|
|
return scaled_view_ops;
|
|
}
|
|
|
|
void
|
|
LayoutCanvas::prepare_drawing ()
|
|
{
|
|
if (m_need_redraw) {
|
|
|
|
BitmapViewObjectCanvas::set_size (m_viewport_l.width (), m_viewport_l.height (), 1.0 / double (m_oversampling * dpr ()));
|
|
|
|
if (! mp_image ||
|
|
(unsigned int) mp_image->width () != m_viewport_l.width () ||
|
|
(unsigned int) mp_image->height () != m_viewport_l.height ()) {
|
|
if (mp_image) {
|
|
delete mp_image;
|
|
}
|
|
mp_image = new tl::PixelBuffer (m_viewport_l.width (), m_viewport_l.height ());
|
|
if (mp_image_fg) {
|
|
delete mp_image_fg;
|
|
mp_image_fg = 0;
|
|
}
|
|
}
|
|
|
|
mp_image->fill (m_background);
|
|
|
|
// Cancel any pending "finish" event so there is no race between finish and restart (important for caching)
|
|
m_do_end_of_drawing_dm.cancel ();
|
|
|
|
// look for a cache entry we may reuse
|
|
std::vector <ImageCacheEntry>::iterator c;
|
|
for (c = m_image_cache.begin (); c != m_image_cache.end (); ++c) {
|
|
if (! c->opened () && c->equals (m_viewport_l, m_layers) && can_restore_data (c->data ())) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (c != m_image_cache.end ()) {
|
|
|
|
// move selected entry to end of cache for renewed life time
|
|
while (c + 1 != m_image_cache.end ()) {
|
|
c->swap (c [1]);
|
|
++c;
|
|
}
|
|
|
|
mp_redraw_thread->commit (m_layers, m_viewport_l, 1.0 / double (m_oversampling * dpr ()));
|
|
|
|
if (tl::verbosity () >= 20) {
|
|
tl::info << "Restored image from cache";
|
|
}
|
|
restore_data (c->data ());
|
|
|
|
} else {
|
|
|
|
bool precious = m_viewport_l.target_box ().equal (m_precious_box);
|
|
|
|
// discard all open cache entries and reset all previously precious ones
|
|
for (size_t i = 0; i < m_image_cache.size (); ++i) {
|
|
if (precious) {
|
|
m_image_cache [i].set_precious (false);
|
|
}
|
|
if (m_image_cache [i].opened () || m_image_cache [i].equals (m_viewport_l, m_layers)) {
|
|
m_image_cache.erase (m_image_cache.begin () + i);
|
|
--i;
|
|
}
|
|
}
|
|
|
|
// retire old entries
|
|
if (m_image_cache_size == 0) {
|
|
|
|
m_image_cache.clear ();
|
|
|
|
} else {
|
|
|
|
if (m_image_cache_size == 1) {
|
|
if (precious || (!m_image_cache.empty () && !m_image_cache.front ().precious ())) {
|
|
m_image_cache.clear ();
|
|
}
|
|
} else if (m_image_cache.size () > m_image_cache_size - 1) {
|
|
for (size_t i = 0; i < m_image_cache.size () && m_image_cache.size () > m_image_cache_size - 1; ++i) {
|
|
if (! m_image_cache [i].precious ()) {
|
|
m_image_cache.erase (m_image_cache.begin () + i);
|
|
--i;
|
|
}
|
|
}
|
|
}
|
|
|
|
// create a new image cache entry
|
|
if (m_image_cache.size () < m_image_cache_size) {
|
|
m_image_cache.push_back (ImageCacheEntry (m_viewport_l, m_layers, precious));
|
|
}
|
|
|
|
}
|
|
|
|
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 * dpr ()), m_redraw_force_update);
|
|
} else {
|
|
mp_redraw_thread->restart (m_need_redraw_layer);
|
|
}
|
|
|
|
}
|
|
|
|
// for short draw jobs, the drawing is already done now. For others display the busy cursor.
|
|
if (mp_redraw_thread->is_running ()) {
|
|
set_default_cursor (lay::Cursor::wait);
|
|
}
|
|
|
|
m_need_redraw = false;
|
|
m_redraw_force_update = false;
|
|
m_update_image = true;
|
|
|
|
}
|
|
}
|
|
|
|
void
|
|
LayoutCanvas::update_image ()
|
|
{
|
|
// this will make the image being redone (except for background objects which will
|
|
// only be redrawn on touch_bg)
|
|
m_update_image = true;
|
|
|
|
update (); // produces a paintEvent()
|
|
}
|
|
|
|
void
|
|
LayoutCanvas::free_resources ()
|
|
{
|
|
if (mp_image_fg) {
|
|
delete mp_image_fg;
|
|
mp_image_fg = 0;
|
|
}
|
|
}
|
|
|
|
#if defined(HAVE_QT)
|
|
void
|
|
LayoutCanvas::paint_event ()
|
|
{
|
|
// this is the update image request
|
|
tl::SelfTimer timer_info (tl::verbosity () >= 41, tl::to_string (QObject::tr ("PaintEvent")));
|
|
|
|
// if required, start the redraw thread ..
|
|
prepare_drawing ();
|
|
|
|
if (mp_image) {
|
|
|
|
// check, if the background needs to be updated
|
|
if (m_update_image || needs_update_bg ()) {
|
|
|
|
if (needs_update_bg () || ! mp_image_bg) {
|
|
|
|
// clear the image and paint the background objects
|
|
mp_image->fill (m_background);
|
|
do_render_bg (m_viewport_l, *this);
|
|
|
|
// save the current background image
|
|
if (mp_image_bg) {
|
|
delete mp_image_bg;
|
|
}
|
|
mp_image_bg = new tl::PixelBuffer (*mp_image);
|
|
|
|
} else {
|
|
// else reuse the saved image
|
|
*mp_image = *mp_image_bg;
|
|
}
|
|
|
|
// render the main bitmaps
|
|
to_image (scaled_view_ops (m_oversampling * dpr ()), dither_pattern (), line_styles (), m_oversampling * dpr (), background_color (), foreground_color (), active_color (), this, *mp_image, m_viewport_l.width (), m_viewport_l.height ());
|
|
|
|
if (mp_image_fg) {
|
|
delete mp_image_fg;
|
|
mp_image_fg = 0;
|
|
}
|
|
|
|
m_update_image = false;
|
|
|
|
}
|
|
|
|
// create a base pixmap consisting of the layout with background
|
|
// and static foreground objects
|
|
|
|
if (! mp_image_fg || needs_update_static () ||
|
|
int (mp_image->width ()) != (int) mp_image_fg->width () * int (m_oversampling) ||
|
|
int (mp_image->height ()) != (int) mp_image_fg->height () * int (m_oversampling)) {
|
|
|
|
if (mp_image_fg) {
|
|
delete mp_image_fg;
|
|
}
|
|
|
|
clear_fg_bitmaps ();
|
|
do_render (m_viewport_l, *this, true);
|
|
|
|
mp_image_fg = new tl::PixelBuffer ();
|
|
|
|
if (fg_bitmaps () > 0) {
|
|
|
|
tl::PixelBuffer full_image (*mp_image);
|
|
bitmaps_to_image (fg_view_op_vector (), fg_bitmap_vector (), dither_pattern (), line_styles (), m_oversampling * dpr (), &full_image, m_viewport_l.width (), m_viewport_l.height (), false, &m_mutex);
|
|
|
|
// render the foreground parts ..
|
|
if (m_oversampling == 1) {
|
|
*mp_image_fg = full_image;
|
|
} else {
|
|
tl::PixelBuffer subsampled_image (m_viewport.width (), m_viewport.height ());
|
|
subsampled_image.set_transparent (mp_image->transparent ());
|
|
subsample (full_image, subsampled_image, m_oversampling, m_gamma);
|
|
*mp_image_fg = subsampled_image;
|
|
}
|
|
|
|
} else if (m_oversampling == 1) {
|
|
|
|
*mp_image_fg = *mp_image;
|
|
|
|
} else {
|
|
|
|
tl::PixelBuffer subsampled_image (m_viewport.width (), m_viewport.height ());
|
|
subsampled_image.set_transparent (mp_image->transparent ());
|
|
subsample (*mp_image, subsampled_image, m_oversampling, m_gamma);
|
|
*mp_image_fg = subsampled_image;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// erase any previous data
|
|
clear_fg_bitmaps ();
|
|
|
|
// render dynamic foreground content
|
|
do_render (m_viewport_l, *this, false);
|
|
|
|
// produce the pixmap first and then overdraw with dynamic content.
|
|
QPainter painter (widget ());
|
|
QImage img = mp_image_fg->to_image ();
|
|
#if QT_VERSION >= 0x050000
|
|
img.setDevicePixelRatio (dpr ());
|
|
#endif
|
|
painter.drawImage (QPoint (0, 0), img);
|
|
|
|
if (fg_bitmaps () > 0) {
|
|
|
|
tl::PixelBuffer full_image (mp_image->width (), mp_image->height ());
|
|
full_image.set_transparent (true);
|
|
full_image.fill (0);
|
|
|
|
bitmaps_to_image (fg_view_op_vector (), fg_bitmap_vector (), dither_pattern (), line_styles (), m_oversampling * dpr (), &full_image, m_viewport_l.width (), m_viewport_l.height (), false, &m_mutex);
|
|
|
|
// render the foreground parts ..
|
|
if (m_oversampling == 1) {
|
|
QImage img = full_image.to_image ();
|
|
#if QT_VERSION >= 0x050000
|
|
img.setDevicePixelRatio (dpr ());
|
|
#endif
|
|
painter.drawImage (QPoint (0, 0), img);
|
|
} else {
|
|
tl::PixelBuffer subsampled_image (m_viewport.width (), m_viewport.height ());
|
|
subsampled_image.set_transparent (true);
|
|
subsample (full_image, subsampled_image, m_oversampling, m_gamma);
|
|
QImage img = subsampled_image.to_image ();
|
|
#if QT_VERSION >= 0x050000
|
|
img.setDevicePixelRatio (dpr ());
|
|
#endif
|
|
painter.drawImage (QPoint (0, 0), img);
|
|
}
|
|
|
|
}
|
|
|
|
// erase dynamic bitmaps
|
|
clear_fg_bitmaps ();
|
|
|
|
#if QT_VERSION < 0x050000
|
|
QApplication::syncX ();
|
|
#endif
|
|
|
|
}
|
|
|
|
}
|
|
#endif
|
|
|
|
class DetachedViewObjectCanvas
|
|
: public BitmapViewObjectCanvas
|
|
{
|
|
public:
|
|
DetachedViewObjectCanvas (tl::Color bg, tl::Color fg, tl::Color ac, unsigned int width_l, unsigned int height_l, double resolution, tl::PixelBuffer *img)
|
|
: 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 () != width_l || img->height () != height_l) {
|
|
mp_image_l = new tl::PixelBuffer (width_l, height_l);
|
|
mp_image_l->set_transparent (img->transparent ());
|
|
mp_image_l->fill (bg.rgb ());
|
|
} else {
|
|
mp_image_l = 0;
|
|
}
|
|
}
|
|
|
|
~DetachedViewObjectCanvas ()
|
|
{
|
|
clear_fg_bitmaps ();
|
|
|
|
if (mp_image_l) {
|
|
delete mp_image_l;
|
|
mp_image_l = 0;
|
|
}
|
|
}
|
|
|
|
tl::Color background_color () const
|
|
{
|
|
return m_bg;
|
|
}
|
|
|
|
tl::Color foreground_color () const
|
|
{
|
|
return m_fg;
|
|
}
|
|
|
|
tl::Color active_color () const
|
|
{
|
|
return m_ac;
|
|
}
|
|
|
|
virtual tl::PixelBuffer *bg_image ()
|
|
{
|
|
return mp_image_l ? mp_image_l : mp_image;
|
|
}
|
|
|
|
void transfer_to_image (const lay::DitherPattern &dp, const lay::LineStyles &ls, unsigned int width, unsigned int height)
|
|
{
|
|
if (mp_image_l) {
|
|
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, 1.0 / resolution (), mp_image_l, mp_image_l->width (), mp_image_l->height (), false, 0);
|
|
subsample (*mp_image_l, *mp_image, os, m_gamma);
|
|
} else {
|
|
bitmaps_to_image (fg_view_op_vector (), fg_bitmap_vector (), dp, ls, 1.0 / resolution (), mp_image, width, height, false, 0);
|
|
}
|
|
clear_fg_bitmaps ();
|
|
}
|
|
|
|
void make_background ()
|
|
{
|
|
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, m_gamma);
|
|
}
|
|
}
|
|
|
|
private:
|
|
tl::Color m_bg, m_fg, m_ac;
|
|
tl::PixelBuffer *mp_image;
|
|
tl::PixelBuffer *mp_image_l;
|
|
double m_gamma;
|
|
};
|
|
|
|
/**
|
|
* @brief A simplistic monochrome canvas
|
|
*
|
|
* NOTE: this canvas does not support background painting (currently the background objects
|
|
* do not support monochrome background painting anyway).
|
|
* Nor does it support subsampling (that would mean grayscale).
|
|
*/
|
|
class DetachedViewObjectCanvasMono
|
|
: public BitmapViewObjectCanvas
|
|
{
|
|
public:
|
|
DetachedViewObjectCanvasMono (bool bg, bool fg, bool ac, unsigned int width, unsigned int height)
|
|
: BitmapViewObjectCanvas (width, height, 1.0),
|
|
m_bg (bg), m_fg (fg), m_ac (ac)
|
|
{
|
|
// .. nothing yet ..
|
|
}
|
|
|
|
~DetachedViewObjectCanvasMono ()
|
|
{
|
|
clear_fg_bitmaps ();
|
|
}
|
|
|
|
tl::Color background_color () const
|
|
{
|
|
return m_bg ? 0xffffffff : 0;
|
|
}
|
|
|
|
tl::Color foreground_color () const
|
|
{
|
|
return m_fg ? 0xffffffff : 0;
|
|
}
|
|
|
|
tl::Color active_color () const
|
|
{
|
|
return m_ac ? 0xffffffff : 0;
|
|
}
|
|
|
|
private:
|
|
bool m_bg, m_fg, m_ac;
|
|
};
|
|
|
|
tl::PixelBuffer
|
|
LayoutCanvas::image (unsigned int width, unsigned int height)
|
|
{
|
|
return image_with_options (width, height, -1, -1, -1.0, tl::Color (), tl::Color (), tl::Color (), db::DBox ());
|
|
}
|
|
|
|
tl::PixelBuffer
|
|
LayoutCanvas::image_with_options (unsigned int width, unsigned int height, int linewidth, int oversampling, double resolution, tl::Color background, tl::Color foreground, tl::Color active, const db::DBox &target_box)
|
|
{
|
|
if (oversampling <= 0) {
|
|
oversampling = m_oversampling;
|
|
}
|
|
if (resolution <= 0.0) {
|
|
resolution = 1.0 / oversampling;
|
|
}
|
|
if (linewidth <= 0) {
|
|
linewidth = 1.0 / resolution + 0.5;
|
|
}
|
|
if (! background.is_valid ()) {
|
|
background = background_color ();
|
|
}
|
|
if (! foreground.is_valid ()) {
|
|
foreground = foreground_color ();
|
|
}
|
|
if (! active.is_valid ()) {
|
|
active = active_color ();
|
|
}
|
|
|
|
// TODO: for other architectures MonoLSB may not be the right format
|
|
tl::PixelBuffer img (width, height);
|
|
|
|
// this may happen for BIG images:
|
|
if (img.width () != width || img.height () != height) {
|
|
throw tl::Exception (tl::to_string (tr ("Unable to create an image with size %dx%d pixels")), width, height);
|
|
}
|
|
|
|
img.fill (background.rgb ());
|
|
|
|
// provide canvas objects for the layout bitmaps and the foreground/background objects
|
|
BitmapRedrawThreadCanvas rd_canvas;
|
|
DetachedViewObjectCanvas vo_canvas (background, foreground, active, width * oversampling, height * oversampling, resolution, &img);
|
|
|
|
// compute the new viewport
|
|
db::DBox tb (target_box);
|
|
if (tb.empty ()) {
|
|
tb = m_viewport.target_box ();
|
|
}
|
|
Viewport vp (width * oversampling, height * oversampling, tb);
|
|
vp.set_global_trans (m_viewport.global_trans ());
|
|
|
|
lay::RedrawThread redraw_thread (&rd_canvas, mp_view);
|
|
|
|
// render the layout
|
|
redraw_thread.start (0 /*synchronous*/, m_layers, vp, resolution, true);
|
|
redraw_thread.stop (); // safety
|
|
|
|
// paint the background objects. It uses "img" to paint on.
|
|
do_render_bg (vp, vo_canvas);
|
|
|
|
// paint the layout bitmaps
|
|
rd_canvas.to_image (scaled_view_ops (linewidth), dither_pattern (), line_styles (), 1.0 / resolution, background, foreground, active, this, *vo_canvas.bg_image (), vp.width (), vp.height ());
|
|
|
|
// subsample current image to provide the background for the foreground objects
|
|
vo_canvas.make_background ();
|
|
|
|
// render the foreground parts ..
|
|
do_render (vp, vo_canvas, true);
|
|
vo_canvas.transfer_to_image (dither_pattern (), line_styles (), width, height);
|
|
|
|
do_render (vp, vo_canvas, false);
|
|
vo_canvas.transfer_to_image (dither_pattern (), line_styles (), width, height);
|
|
|
|
return img;
|
|
}
|
|
|
|
tl::BitmapBuffer
|
|
LayoutCanvas::image_with_options_mono (unsigned int width, unsigned int height, int linewidth, tl::Color background_c, tl::Color foreground_c, tl::Color active_c, const db::DBox &target_box)
|
|
{
|
|
if (linewidth <= 0) {
|
|
linewidth = 1;
|
|
}
|
|
|
|
bool background = background_c.is_valid () ? background_c.to_mono () : background_color ().to_mono ();
|
|
bool foreground = foreground_c.is_valid () ? foreground_c.to_mono () : foreground_color ().to_mono ();
|
|
bool active = active_c.is_valid () ? active_c.to_mono () : active_color ().to_mono ();
|
|
|
|
// provide canvas objects for the layout bitmaps and the foreground/background objects
|
|
BitmapRedrawThreadCanvas rd_canvas;
|
|
DetachedViewObjectCanvasMono vo_canvas (background, foreground, active, width, height);
|
|
|
|
// compute the new viewport
|
|
db::DBox tb (target_box);
|
|
if (tb.empty ()) {
|
|
tb = m_viewport.target_box ();
|
|
}
|
|
Viewport vp (width, height, tb);
|
|
vp.set_global_trans (m_viewport.global_trans ());
|
|
|
|
lay::RedrawThread redraw_thread (&rd_canvas, mp_view);
|
|
|
|
// render the layout
|
|
redraw_thread.start (0 /*synchronous*/, m_layers, vp, 1.0, true);
|
|
redraw_thread.stop (); // safety
|
|
|
|
tl::BitmapBuffer img (width, height);
|
|
img.fill (background);
|
|
|
|
rd_canvas.to_image_mono (scaled_view_ops (linewidth), dither_pattern (), line_styles (), linewidth, background, foreground, active, this, img, vp.width (), vp.height ());
|
|
|
|
return img;
|
|
}
|
|
|
|
tl::PixelBuffer
|
|
LayoutCanvas::screenshot ()
|
|
{
|
|
// if required, start the redraw thread ..
|
|
prepare_drawing ();
|
|
|
|
tl::PixelBuffer img (m_viewport.width (), m_viewport.height ());
|
|
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 * dpr ()), &img);
|
|
|
|
// and paint the background objects. It uses "img" to paint on.
|
|
do_render_bg (m_viewport_l, vo_canvas);
|
|
|
|
// paint the layout bitmaps
|
|
to_image (scaled_view_ops (m_oversampling * dpr ()), dither_pattern (), line_styles (), m_oversampling * dpr (), background_color (), foreground_color (), active_color (), this, *vo_canvas.bg_image (), m_viewport_l.width (), m_viewport_l.height ());
|
|
|
|
// subsample current image to provide the background for the foreground objects
|
|
vo_canvas.make_background ();
|
|
|
|
// render the foreground parts ..
|
|
do_render (m_viewport_l, vo_canvas, true);
|
|
vo_canvas.transfer_to_image (dither_pattern (), line_styles (), m_viewport.width (), m_viewport.height ());
|
|
|
|
do_render (m_viewport_l, vo_canvas, false);
|
|
vo_canvas.transfer_to_image (dither_pattern (), line_styles (), m_viewport.width (), m_viewport.height ());
|
|
|
|
return img;
|
|
}
|
|
|
|
void
|
|
LayoutCanvas::resize_event (unsigned int width, unsigned int height)
|
|
{
|
|
unsigned int w = width * dpr () + 0.5, h = height * dpr () + 0.5;
|
|
unsigned int wl = width * m_oversampling * dpr () + 0.5, hl = height * m_oversampling * dpr () + 0.5;
|
|
|
|
if (m_viewport.width () != w || m_viewport.height () != h ||
|
|
m_viewport_l.width () != wl || m_viewport_l.height () != hl) {
|
|
|
|
// clear the image cache
|
|
m_image_cache.clear ();
|
|
|
|
// set the viewport to the new size
|
|
m_viewport.set_size (width * dpr () + 0.5, height * dpr () + 0.5);
|
|
m_viewport_l.set_size (width * m_oversampling * dpr () + 0.5, height * m_oversampling * dpr () + 0.5);
|
|
|
|
mouse_event_trans (db::DCplxTrans (1.0 / dpr ()) * m_viewport.trans ());
|
|
do_redraw_all (true);
|
|
viewport_changed_event ();
|
|
|
|
}
|
|
}
|
|
|
|
void
|
|
LayoutCanvas::update_viewport ()
|
|
{
|
|
mouse_event_trans (db::DCplxTrans (1.0 / dpr ()) * m_viewport.trans ());
|
|
for (service_iterator svc = begin_services (); svc != end_services (); ++svc) {
|
|
(*svc)->update ();
|
|
}
|
|
do_redraw_all (false);
|
|
viewport_changed_event ();
|
|
}
|
|
|
|
const db::DCplxTrans &
|
|
LayoutCanvas::global_trans () const
|
|
{
|
|
return m_viewport.global_trans ();
|
|
}
|
|
|
|
void
|
|
LayoutCanvas::set_global_trans (const db::DCplxTrans &global_trans)
|
|
{
|
|
m_viewport.set_global_trans (global_trans);
|
|
m_viewport_l.set_global_trans (global_trans);
|
|
update_viewport ();
|
|
}
|
|
|
|
void
|
|
LayoutCanvas::zoom_box (const db::DBox &box, bool precious)
|
|
{
|
|
if (precious) {
|
|
m_precious_box = box;
|
|
}
|
|
m_viewport.set_box (box);
|
|
m_viewport_l.set_box (box);
|
|
update_viewport ();
|
|
}
|
|
|
|
void
|
|
LayoutCanvas::zoom_trans (const db::DCplxTrans &trans)
|
|
{
|
|
m_viewport.set_trans (trans);
|
|
m_viewport_l.set_trans (db::DCplxTrans (double (m_oversampling)) * trans);
|
|
update_viewport ();
|
|
}
|
|
|
|
bool
|
|
LayoutCanvas::drawing_finished ()
|
|
{
|
|
bool f = m_drawing_finished;
|
|
m_drawing_finished = false;
|
|
return f;
|
|
}
|
|
|
|
void
|
|
LayoutCanvas::do_end_of_drawing ()
|
|
{
|
|
// store the data into the open entries or discard if not compatible
|
|
for (size_t i = 0; i < m_image_cache.size (); ++i) {
|
|
if (m_image_cache [i].opened ()) {
|
|
if (m_image_cache [i].equals (m_viewport_l, m_layers)) {
|
|
m_image_cache.back ().close (store_data ());
|
|
} else {
|
|
m_image_cache.erase (m_image_cache.begin () + i);
|
|
--i;
|
|
}
|
|
}
|
|
}
|
|
|
|
set_default_cursor (lay::Cursor::none);
|
|
|
|
m_drawing_finished = true;
|
|
}
|
|
|
|
void
|
|
LayoutCanvas::do_update_image ()
|
|
{
|
|
update_image ();
|
|
}
|
|
|
|
#if defined(HAVE_QT)
|
|
void
|
|
LayoutCanvas::gtf_probe ()
|
|
{
|
|
if (gtf::Recorder::instance () && gtf::Recorder::instance ()->recording ()) {
|
|
gtf::Recorder::instance ()->probe (widget (), gtf::image_to_variant (screenshot ().to_image_copy ()));
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void
|
|
LayoutCanvas::redraw_all ()
|
|
{
|
|
do_redraw_all ();
|
|
}
|
|
|
|
void
|
|
LayoutCanvas::do_redraw_all (bool force_redraw)
|
|
{
|
|
stop_redraw ();
|
|
|
|
if (! m_need_redraw) {
|
|
m_need_redraw_layer.clear ();
|
|
}
|
|
|
|
m_need_redraw = true;
|
|
m_redraw_clearing = true;
|
|
if (force_redraw) {
|
|
m_redraw_force_update = true;
|
|
}
|
|
|
|
// redraw the background elements
|
|
touch_bg ();
|
|
|
|
update (); // produces a paintEvent()
|
|
}
|
|
|
|
void
|
|
LayoutCanvas::redraw_new (std::vector<lay::RedrawLayerInfo> &layers)
|
|
{
|
|
m_image_cache.clear ();
|
|
m_layers.swap (layers);
|
|
do_redraw_all (true);
|
|
}
|
|
|
|
void
|
|
LayoutCanvas::redraw_selected (const std::vector<int> &layers)
|
|
{
|
|
stop_redraw ();
|
|
|
|
m_image_cache.clear ();
|
|
|
|
if (! m_need_redraw) {
|
|
m_redraw_clearing = false;
|
|
m_need_redraw_layer.clear ();
|
|
}
|
|
|
|
m_need_redraw = true;
|
|
m_need_redraw_layer.insert (m_need_redraw_layer.end (), layers.begin (), layers.end ());
|
|
std::sort (m_need_redraw_layer.begin (), m_need_redraw_layer.end ());
|
|
m_need_redraw_layer.erase (std::unique (m_need_redraw_layer.begin (), m_need_redraw_layer.end ()), m_need_redraw_layer.end ());
|
|
m_redraw_force_update = true;
|
|
|
|
update (); // produces a paintEvent()
|
|
}
|
|
|
|
void
|
|
LayoutCanvas::change_visibility (const std::vector <bool> &visible)
|
|
{
|
|
stop_redraw ();
|
|
mp_redraw_thread->change_visibility (visible);
|
|
for (unsigned int i = 0; i < visible.size () && i < m_layers.size (); ++i) {
|
|
m_layers [i].visible = visible [i];
|
|
}
|
|
|
|
if (! m_need_redraw) {
|
|
m_redraw_clearing = false;
|
|
}
|
|
|
|
m_need_redraw = true;
|
|
m_need_redraw_layer.clear ();
|
|
|
|
update (); // produces a paintEvent()
|
|
}
|
|
|
|
void
|
|
LayoutCanvas::stop_redraw ()
|
|
{
|
|
// discard all open cache entries
|
|
for (size_t i = 0; i < m_image_cache.size (); ++i) {
|
|
if (m_image_cache [i].opened ()) {
|
|
m_image_cache.erase (m_image_cache.begin () + i);
|
|
--i;
|
|
}
|
|
}
|
|
|
|
mp_redraw_thread->stop ();
|
|
}
|
|
|
|
void
|
|
LayoutCanvas::update_drawings ()
|
|
{
|
|
update_image ();
|
|
}
|
|
|
|
void
|
|
LayoutCanvas::signal_transfer_done ()
|
|
{
|
|
m_do_update_image_dm ();
|
|
}
|
|
|
|
void
|
|
LayoutCanvas::signal_end_of_drawing ()
|
|
{
|
|
m_do_end_of_drawing_dm ();
|
|
}
|
|
|
|
} // namespace lay
|
|
|