11// SPDX-License-Identifier: LGPL-3.0-or-later
22
3+ #include < scratchcpp/costume.h>
4+
35#include " penlayer.h"
46#include " penlayerpainter.h"
57#include " penattributes.h"
8+ #include " irenderedtarget.h"
9+ #include " spritemodel.h"
10+ #include " stagemodel.h"
611
712using namespace scratchcpprender ;
813
@@ -18,6 +23,12 @@ PenLayer::~PenLayer()
1823{
1924 if (m_engine)
2025 m_projectPenLayers.erase (m_engine);
26+
27+ if (m_blitter.isCreated ()) {
28+ // Delete vertex array and buffer
29+ m_glF->glDeleteVertexArrays (1 , &m_vao);
30+ m_glF->glDeleteBuffers (1 , &m_vbo);
31+ }
2132}
2233
2334bool PenLayer::antialiasingEnabled () const
@@ -56,6 +67,39 @@ void PenLayer::setEngine(libscratchcpp::IEngine *newEngine)
5667 if (!m_painter)
5768 m_painter = std::make_unique<QNanoPainter>();
5869
70+ if (!m_glF) {
71+ m_glF = std::make_unique<QOpenGLExtraFunctions>();
72+ m_glF->initializeOpenGLFunctions ();
73+ }
74+
75+ if (!m_blitter.isCreated ()) {
76+ m_blitter.create ();
77+
78+ // Set up VBO and VAO
79+ float vertices[] = {
80+ -1 .0f , -1 .0f , 0 .0f , 0 .0f , 1 .0f , -1 .0f , 1 .0f , 0 .0f , -1 .0f , 1 .0f , 0 .0f , 1 .0f , 1 .0f , 1 .0f , 1 .0f , 1 .0f ,
81+ };
82+
83+ m_glF->glGenVertexArrays (1 , &m_vao);
84+ m_glF->glGenBuffers (1 , &m_vbo);
85+
86+ m_glF->glBindVertexArray (m_vao);
87+
88+ m_glF->glBindBuffer (GL_ARRAY_BUFFER, m_vbo);
89+ m_glF->glBufferData (GL_ARRAY_BUFFER, sizeof (vertices), vertices, GL_STATIC_DRAW);
90+
91+ // Position attribute
92+ m_glF->glVertexAttribPointer (0 , 2 , GL_FLOAT, GL_FALSE, 4 * sizeof (float ), (void *)0 );
93+ m_glF->glEnableVertexAttribArray (0 );
94+
95+ // Texture coordinate attribute
96+ m_glF->glVertexAttribPointer (1 , 2 , GL_FLOAT, GL_FALSE, 4 * sizeof (float ), (void *)(2 * sizeof (float )));
97+ m_glF->glEnableVertexAttribArray (1 );
98+
99+ m_glF->glBindVertexArray (0 );
100+ m_glF->glBindBuffer (GL_ARRAY_BUFFER, 0 );
101+ }
102+
59103 clear ();
60104 }
61105
@@ -67,11 +111,6 @@ void scratchcpprender::PenLayer::clear()
67111 if (!m_fbo)
68112 return ;
69113
70- if (!m_glF) {
71- m_glF = std::make_unique<QOpenGLFunctions>();
72- m_glF->initializeOpenGLFunctions ();
73- }
74-
75114 m_fbo->bind ();
76115 m_glF->glDisable (GL_SCISSOR_TEST);
77116 m_glF->glClearColor (0 .0f , 0 .0f , 0 .0f , 0 .0f );
@@ -138,6 +177,165 @@ void scratchcpprender::PenLayer::drawLine(const PenAttributes &penAttributes, do
138177 update ();
139178}
140179
180+ /*
181+ * A brief description of how stamping is implemented:
182+ * 1. Get rotation, size and coordinates and translate them.
183+ * 2. Draw the texture onto a temporary texture using shaders.
184+ * 3. Blit the resulting texture to a FBO with a square texture (required for rotation).
185+ * 4. Blit the resulting texture to the pen layer using QOpenGLTextureBlitter with transform.
186+ *
187+ * If you think this is too complicated, contributions are welcome!
188+ */
189+ void PenLayer::stamp (IRenderedTarget *target)
190+ {
191+ if (!target || !m_fbo || !m_texture.isValid () || !m_blitter.isCreated ())
192+ return ;
193+
194+ double x = 0 ;
195+ double y = 0 ;
196+ double angle = 0 ;
197+ double scale = 1 ;
198+ bool mirror = false ;
199+ std::shared_ptr<libscratchcpp::Costume> costume;
200+
201+ SpriteModel *spriteModel = target->spriteModel ();
202+
203+ if (spriteModel) {
204+ libscratchcpp::Sprite *sprite = spriteModel->sprite ();
205+ x = sprite->x ();
206+ y = sprite->y ();
207+
208+ switch (sprite->rotationStyle ()) {
209+ case libscratchcpp::Sprite::RotationStyle::AllAround:
210+ angle = 90 - sprite->direction ();
211+ break ;
212+
213+ case libscratchcpp::Sprite::RotationStyle::LeftRight:
214+ mirror = (sprite->direction () < 0 );
215+ break ;
216+
217+ default :
218+ break ;
219+ }
220+
221+ scale = sprite->size () / 100 ;
222+ costume = sprite->currentCostume ();
223+ } else
224+ costume = target->stageModel ()->stage ()->currentCostume ();
225+
226+ const double bitmapRes = costume->bitmapResolution ();
227+ const double centerX = costume->rotationCenterX () / bitmapRes;
228+ const double centerY = costume->rotationCenterY () / bitmapRes;
229+
230+ const Texture &texture = target->cpuTexture ();
231+
232+ if (!texture.isValid ())
233+ return ;
234+
235+ const double textureScale = texture.width () / static_cast <double >(target->costumeWidth ());
236+
237+ // Translate the coordinates
238+ // TODO: Apply scale (HQ pen)
239+ x = std::floor (x + m_texture.width () / 2.0 );
240+ y = std::floor (-y + m_texture.height () / 2.0 );
241+
242+ m_glF->glDisable (GL_SCISSOR_TEST);
243+
244+ // For some reason nothing is rendered without this
245+ // TODO: Find out why this is happening
246+ m_painter->beginFrame (m_fbo->width (), m_fbo->height ());
247+ m_painter->stroke ();
248+ m_painter->endFrame ();
249+
250+ // Create a temporary FBO for graphic effects
251+ QOpenGLFramebufferObject tmpFbo (texture.size ());
252+ m_painter->beginFrame (tmpFbo.width (), tmpFbo.height ());
253+
254+ // Create a FBO for the current texture
255+ unsigned int fbo;
256+ m_glF->glGenFramebuffers (1 , &fbo);
257+ m_glF->glBindFramebuffer (GL_FRAMEBUFFER, fbo);
258+ m_glF->glFramebufferTexture2D (GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture.handle (), 0 );
259+
260+ if (m_glF->glCheckFramebufferStatus (GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
261+ qWarning () << " error: framebuffer incomplete (stamp " + target->scratchTarget ()->name () + " )" ;
262+ m_glF->glDeleteFramebuffers (1 , &fbo);
263+ return ;
264+ }
265+
266+ // Get the shader program for the current set of effects
267+ ShaderManager *shaderManager = ShaderManager::instance ();
268+
269+ const auto &effects = target->graphicEffects ();
270+ QOpenGLShaderProgram *shaderProgram = shaderManager->getShaderProgram (effects);
271+ Q_ASSERT (shaderProgram);
272+ Q_ASSERT (shaderProgram->isLinked ());
273+
274+ m_glF->glBindBuffer (GL_ARRAY_BUFFER, m_vbo);
275+
276+ // Render to the target framebuffer
277+ m_glF->glBindFramebuffer (GL_FRAMEBUFFER, tmpFbo.handle ());
278+ shaderProgram->bind ();
279+ m_glF->glBindVertexArray (m_vao);
280+ m_glF->glActiveTexture (GL_TEXTURE0);
281+ m_glF->glBindTexture (GL_TEXTURE_2D, texture.handle ());
282+ shaderManager->setUniforms (shaderProgram, 0 , effects); // set texture and effect uniforms
283+ m_glF->glDrawArrays (GL_TRIANGLE_STRIP, 0 , 4 );
284+
285+ m_painter->endFrame ();
286+
287+ // Resize to square (for rotation)
288+ const double dim = std::max (tmpFbo.width (), tmpFbo.height ());
289+ QOpenGLFramebufferObject resizeFbo (dim, dim);
290+ resizeFbo.bind ();
291+ m_painter->beginFrame (dim, dim);
292+
293+ const QRect resizeRect (QPoint (0 , 0 ), tmpFbo.size ());
294+ const QMatrix4x4 matrix = QOpenGLTextureBlitter::targetTransform (resizeRect, QRect (QPoint (0 , 0 ), resizeFbo.size ()));
295+ m_glF->glClearColor (0 .0f , 0 .0f , 0 .0f , 0 .0f );
296+ m_glF->glClear (GL_COLOR_BUFFER_BIT);
297+ m_blitter.bind ();
298+ m_blitter.blit (tmpFbo.texture (), matrix, QOpenGLTextureBlitter::OriginBottomLeft);
299+ m_blitter.release ();
300+
301+ m_painter->endFrame ();
302+ resizeFbo.release ();
303+
304+ // Cleanup
305+ shaderProgram->release ();
306+ m_glF->glBindVertexArray (0 );
307+ m_glF->glBindBuffer (GL_ARRAY_BUFFER, 0 );
308+ m_glF->glBindFramebuffer (GL_FRAMEBUFFER, 0 );
309+ m_glF->glDeleteFramebuffers (1 , &fbo);
310+
311+ // Transform
312+ const double width = resizeFbo.width () / textureScale;
313+ const double height = resizeFbo.height () / textureScale;
314+ QRectF targetRect (QPoint (x, y), QSizeF (width, height));
315+ QTransform transform = QOpenGLTextureBlitter::targetTransform (targetRect, QRect (QPoint (centerX, centerY), m_fbo->size ())).toTransform ();
316+ const double dx = 2 * (centerX - width / 2.0 ) / width;
317+ const double dy = -2 * (centerY - height / 2.0 ) / height;
318+ transform.translate (dx, dy);
319+ transform.rotate (angle);
320+ transform.scale (scale * (mirror ? -1 : 1 ), scale);
321+ transform.translate (-dx, -dy);
322+
323+ // Blit
324+ m_fbo->bind ();
325+ m_painter->beginFrame (m_fbo->width (), m_fbo->height ());
326+ m_blitter.bind ();
327+ m_blitter.blit (resizeFbo.texture (), transform, QOpenGLTextureBlitter::OriginBottomLeft);
328+ m_blitter.release ();
329+ m_painter->endFrame ();
330+ m_fbo->release ();
331+
332+ m_glF->glEnable (GL_SCISSOR_TEST);
333+
334+ m_textureDirty = true ;
335+ m_boundsDirty = true ;
336+ update ();
337+ }
338+
141339QOpenGLFramebufferObject *PenLayer::framebufferObject () const
142340{
143341 return m_fbo.get ();
0 commit comments