Skip to content

Commit 90a36a6

Browse files
authored
Merge pull request #128 from scratchcpp/stamp
Implement stamp block
2 parents 9bf9f6b + 58ba361 commit 90a36a6

16 files changed

+408
-7
lines changed

src/blocks/penblocks.cpp

+31
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@
22

33
#include <scratchcpp/compiler.h>
44
#include <scratchcpp/sprite.h>
5+
#include <scratchcpp/stage.h>
56
#include <scratchcpp/input.h>
67

78
#include "penblocks.h"
89
#include "penlayer.h"
910
#include "penstate.h"
1011
#include "spritemodel.h"
12+
#include "stagemodel.h"
1113

1214
using namespace scratchcpprender;
1315
using namespace libscratchcpp;
@@ -31,6 +33,7 @@ void PenBlocks::registerBlocks(IEngine *engine)
3133
{
3234
// Blocks
3335
engine->addCompileFunction(this, "pen_clear", &compileClear);
36+
engine->addCompileFunction(this, "pen_stamp", &compileStamp);
3437
engine->addCompileFunction(this, "pen_penDown", &compilePenDown);
3538
engine->addCompileFunction(this, "pen_penUp", &compilePenUp);
3639
engine->addCompileFunction(this, "pen_setPenColorToColor", &compileSetPenColorToColor);
@@ -57,6 +60,11 @@ void PenBlocks::compileClear(Compiler *compiler)
5760
compiler->addFunctionCall(&clear);
5861
}
5962

63+
void PenBlocks::compileStamp(Compiler *compiler)
64+
{
65+
compiler->addFunctionCall(&stamp);
66+
}
67+
6068
void PenBlocks::compilePenDown(Compiler *compiler)
6169
{
6270
compiler->addFunctionCall(&penDown);
@@ -179,6 +187,29 @@ unsigned int PenBlocks::clear(VirtualMachine *vm)
179187
return 0;
180188
}
181189

190+
unsigned int PenBlocks::stamp(libscratchcpp::VirtualMachine *vm)
191+
{
192+
IPenLayer *penLayer = PenLayer::getProjectPenLayer(vm->engine());
193+
194+
if (penLayer) {
195+
Target *target = vm->target();
196+
IRenderedTarget *renderedTarget = nullptr;
197+
198+
if (target->isStage()) {
199+
IStageHandler *iface = static_cast<Stage *>(target)->getInterface();
200+
renderedTarget = static_cast<StageModel *>(iface)->renderedTarget();
201+
} else {
202+
ISpriteHandler *iface = static_cast<Sprite *>(target)->getInterface();
203+
renderedTarget = static_cast<SpriteModel *>(iface)->renderedTarget();
204+
}
205+
206+
penLayer->stamp(renderedTarget);
207+
vm->engine()->requestRedraw();
208+
}
209+
210+
return 0;
211+
}
212+
182213
unsigned int PenBlocks::penDown(VirtualMachine *vm)
183214
{
184215
SpriteModel *model = getSpriteModel(vm);

src/blocks/penblocks.h

+2
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ class PenBlocks : public libscratchcpp::IBlockSection
2929
void registerBlocks(libscratchcpp::IEngine *engine) override;
3030

3131
static void compileClear(libscratchcpp::Compiler *compiler);
32+
static void compileStamp(libscratchcpp::Compiler *compiler);
3233
static void compilePenDown(libscratchcpp::Compiler *compiler);
3334
static void compilePenUp(libscratchcpp::Compiler *compiler);
3435
static void compileSetPenColorToColor(libscratchcpp::Compiler *compiler);
@@ -42,6 +43,7 @@ class PenBlocks : public libscratchcpp::IBlockSection
4243
static void compileSetPenHueToNumber(libscratchcpp::Compiler *compiler);
4344

4445
static unsigned int clear(libscratchcpp::VirtualMachine *vm);
46+
static unsigned int stamp(libscratchcpp::VirtualMachine *vm);
4547
static unsigned int penDown(libscratchcpp::VirtualMachine *vm);
4648
static unsigned int penUp(libscratchcpp::VirtualMachine *vm);
4749
static unsigned int setPenColorToColor(libscratchcpp::VirtualMachine *vm);

src/ipenlayer.h

+2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ namespace scratchcpprender
1616
{
1717

1818
struct PenAttributes;
19+
class IRenderedTarget;
1920

2021
class IPenLayer : public QNanoQuickItem
2122
{
@@ -36,6 +37,7 @@ class IPenLayer : public QNanoQuickItem
3637
virtual void clear() = 0;
3738
virtual void drawPoint(const PenAttributes &penAttributes, double x, double y) = 0;
3839
virtual void drawLine(const PenAttributes &penAttributes, double x0, double y0, double x1, double y1) = 0;
40+
virtual void stamp(IRenderedTarget *target) = 0;
3941

4042
virtual QOpenGLFramebufferObject *framebufferObject() const = 0;
4143
virtual QRgb colorAtScratchPoint(double x, double y) const = 0;

src/irenderedtarget.h

+1
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ class IRenderedTarget : public QNanoQuickItem
7878
virtual bool mirrorHorizontally() const = 0;
7979

8080
virtual Texture texture() const = 0;
81+
virtual const Texture &cpuTexture() const = 0;
8182
virtual int costumeWidth() const = 0;
8283
virtual int costumeHeight() const = 0;
8384

src/penlayer.cpp

+203-5
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
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

712
using 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

2334
bool 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+
141339
QOpenGLFramebufferObject *PenLayer::framebufferObject() const
142340
{
143341
return m_fbo.get();

src/penlayer.h

+6-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
#pragma once
44

55
#include <QOpenGLFramebufferObject>
6-
#include <QOpenGLFunctions>
6+
#include <QOpenGLExtraFunctions>
77
#include <qnanopainter.h>
88
#include <scratchcpp/iengine.h>
99

@@ -33,6 +33,7 @@ class PenLayer : public IPenLayer
3333
void clear() override;
3434
void drawPoint(const PenAttributes &penAttributes, double x, double y) override;
3535
void drawLine(const PenAttributes &penAttributes, double x0, double y0, double x1, double y1) override;
36+
void stamp(IRenderedTarget *target) override;
3637

3738
QOpenGLFramebufferObject *framebufferObject() const override;
3839
QRgb colorAtScratchPoint(double x, double y) const override;
@@ -56,12 +57,15 @@ class PenLayer : public IPenLayer
5657
libscratchcpp::IEngine *m_engine = nullptr;
5758
std::unique_ptr<QOpenGLFramebufferObject> m_fbo;
5859
std::unique_ptr<QNanoPainter> m_painter;
59-
std::unique_ptr<QOpenGLFunctions> m_glF;
60+
std::unique_ptr<QOpenGLExtraFunctions> m_glF;
6061
Texture m_texture;
6162
bool m_textureDirty = true;
6263
mutable CpuTextureManager m_textureManager;
6364
mutable bool m_boundsDirty = true;
6465
mutable libscratchcpp::Rect m_bounds;
66+
QOpenGLTextureBlitter m_blitter;
67+
GLuint m_vbo = 0;
68+
GLuint m_vao = 0;
6569
};
6670

6771
} // namespace scratchcpprender

src/renderedtarget.cpp

+5
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,11 @@ Texture RenderedTarget::texture() const
529529
return m_texture;
530530
}
531531

532+
const Texture &RenderedTarget::cpuTexture() const
533+
{
534+
return m_cpuTexture;
535+
}
536+
532537
int RenderedTarget::costumeWidth() const
533538
{
534539
if (!m_skin || !m_costume)

0 commit comments

Comments
 (0)