#include "yacreader_flow_gl.h" #include #include #include // Structure for per-instance data struct InstanceData { QMatrix4x4 modelMatrix; float leftUpShading; float leftDownShading; float rightUpShading; float rightDownShading; float opacity; float padding[3]; // Align to 16 bytes }; /*** Preset Configurations ***/ int YACReaderFlowGL::updateInterval = 16; struct Preset defaultYACReaderFlowConfig = { 0.08f, // Animation_step sets the speed of the animation 1.5f, // Animation_speedup sets the acceleration of the animation 0.1f, // Animation_step_max sets the maximum speed of the animation 3.f, // Animation_Fade_out_dis sets the distance of view 1.5f, // pre_rotation sets the rotation increasion 3.f, // View_rotate_light_strenght sets the light strenght on rotation 0.01f, // View_rotate_add sets the speed of the rotation 0.02f, // View_rotate_sub sets the speed of reversing the rotation 20.f, // View_angle sets the maximum view angle 0.f, // CF_X the X Position of the Coverflow 0.f, // CF_Y the Y Position of the Coverflow -8.f, // CF_Z the Z Position of the Coverflow 15.f, // CF_RX the X Rotation of the Coverflow 0.f, // CF_RY the Y Rotation of the Coverflow 0.f, // CF_RZ the Z Rotation of the Coverflow -50.f, // Rotation sets the rotation of each cover 0.18f, // X_Distance sets the distance between the covers 1.f, // Center_Distance sets the distance between the centered and the non centered covers 0.1f, // Z_Distance sets the pushback amount 0.0f, // Y_Distance sets the elevation amount 30.f // zoom level }; struct Preset presetYACReaderFlowClassicConfig = { 0.08f, // Animation_step sets the speed of the animation 1.5f, // Animation_speedup sets the acceleration of the animation 0.1f, // Animation_step_max sets the maximum speed of the animation 2.f, // Animation_Fade_out_dis sets the distance of view 1.5f, // pre_rotation sets the rotation increasion 3.f, // View_rotate_light_strenght sets the light strenght on rotation 0.08f, // View_rotate_add sets the speed of the rotation 0.08f, // View_rotate_sub sets the speed of reversing the rotation 30.f, // View_angle sets the maximum view angle 0.f, // CF_X the X Position of the Coverflow -0.2f, // CF_Y the Y Position of the Coverflow -7.f, // CF_Z the Z Position of the Coverflow 0.f, // CF_RX the X Rotation of the Coverflow 0.f, // CF_RY the Y Rotation of the Coverflow 0.f, // CF_RZ the Z Rotation of the Coverflow -40.f, // Rotation sets the rotation of each cover 0.18f, // X_Distance sets the distance between the covers 1.f, // Center_Distance sets the distance between the centered and the non centered covers 0.1f, // Z_Distance sets the pushback amount 0.0f, // Y_Distance sets the elevation amount 22.f // zoom level }; struct Preset presetYACReaderFlowStripeConfig = { 0.08f, // Animation_step sets the speed of the animation 1.5f, // Animation_speedup sets the acceleration of the animation 0.1f, // Animation_step_max sets the maximum speed of the animation 6.f, // Animation_Fade_out_dis sets the distance of view 1.5f, // pre_rotation sets the rotation increasion 4.f, // View_rotate_light_strenght sets the light strenght on rotation 0.08f, // View_rotate_add sets the speed of the rotation 0.08f, // View_rotate_sub sets the speed of reversing the rotation 30.f, // View_angle sets the maximum view angle 0.f, // CF_X the X Position of the Coverflow -0.2f, // CF_Y the Y Position of the Coverflow -7.f, // CF_Z the Z Position of the Coverflow 0.f, // CF_RX the X Rotation of the Coverflow 0.f, // CF_RY the Y Rotation of the Coverflow 0.f, // CF_RZ the Z Rotation of the Coverflow 0.f, // Rotation sets the rotation of each cover 1.1f, // X_Distance sets the distance between the covers 0.2f, // Center_Distance sets the distance between the centered and the non centered covers 0.01f, // Z_Distance sets the pushback amount 0.0f, // Y_Distance sets the elevation amount 22.f // zoom level }; struct Preset presetYACReaderFlowOverlappedStripeConfig = { 0.08f, // Animation_step sets the speed of the animation 1.5f, // Animation_speedup sets the acceleration of the animation 0.1f, // Animation_step_max sets the maximum speed of the animation 2.f, // Animation_Fade_out_dis sets the distance of view 1.5f, // pre_rotation sets the rotation increasion 3.f, // View_rotate_light_strenght sets the light strenght on rotation 0.08f, // View_rotate_add sets the speed of the rotation 0.08f, // View_rotate_sub sets the speed of reversing the rotation 30.f, // View_angle sets the maximum view angle 0.f, // CF_X the X Position of the Coverflow -0.2f, // CF_Y the Y Position of the Coverflow -7.f, // CF_Z the Z Position of the Coverflow 0.f, // CF_RX the X Rotation of the Coverflow 0.f, // CF_RY the Y Rotation of the Coverflow 0.f, // CF_RZ the Z Rotation of the Coverflow 0.f, // Rotation sets the rotation of each cover 0.18f, // X_Distance sets the distance between the covers 1.f, // Center_Distance sets the distance between the centered and the non centered covers 0.1f, // Z_Distance sets the pushback amount 0.0f, // Y_Distance sets the elevation amount 22.f // zoom level }; struct Preset pressetYACReaderFlowUpConfig = { 0.08f, // Animation_step sets the speed of the animation 1.5f, // Animation_speedup sets the acceleration of the animation 0.1f, // Animation_step_max sets the maximum speed of the animation 2.5f, // Animation_Fade_out_dis sets the distance of view 1.5f, // pre_rotation sets the rotation increasion 3.f, // View_rotate_light_strenght sets the light strenght on rotation 0.08f, // View_rotate_add sets the speed of the rotation 0.08f, // View_rotate_sub sets the speed of reversing the rotation 5.f, // View_angle sets the maximum view angle 0.f, // CF_X the X Position of the Coverflow -0.2f, // CF_Y the Y Position of the Coverflow -7.f, // CF_Z the Z Position of the Coverflow 0.f, // CF_RX the X Rotation of the Coverflow 0.f, // CF_RY the Y Rotation of the Coverflow 0.f, // CF_RZ the Z Rotation of the Coverflow -50.f, // Rotation sets the rotation of each cover 0.18f, // X_Distance sets the distance between the covers 1.f, // Center_Distance sets the distance between the centered and the non centered covers 0.1f, // Z_Distance sets the pushback amount -0.1f, // Y_Distance sets the elevation amount 22.f // zoom level }; struct Preset pressetYACReaderFlowDownConfig = { 0.08f, // Animation_step sets the speed of the animation 1.5f, // Animation_speedup sets the acceleration of the animation 0.1f, // Animation_step_max sets the maximum speed of the animation 2.5f, // Animation_Fade_out_dis sets the distance of view 1.5f, // pre_rotation sets the rotation increasion 3.f, // View_rotate_light_strenght sets the light strenght on rotation 0.08f, // View_rotate_add sets the speed of the rotation 0.08f, // View_rotate_sub sets the speed of reversing the rotation 5.f, // View_angle sets the maximum view angle 0.f, // CF_X the X Position of the Coverflow -0.2f, // CF_Y the Y Position of the Coverflow -7.f, // CF_Z the Z Position of the Coverflow 0.f, // CF_RX the X Rotation of the Coverflow 0.f, // CF_RY the Y Rotation of the Coverflow 0.f, // CF_RZ the Z Rotation of the Coverflow -50.f, // Rotation sets the rotation of each cover 0.18f, // X_Distance sets the distance between the covers 1.f, // Center_Distance sets the distance between the centered and the non centered covers 0.1f, // Z_Distance sets the pushback amount 0.1f, // Y_Distance sets the elevation amount 22.f // zoom level }; /*Constructor*/ YACReaderFlowGL::YACReaderFlowGL(QWidget *parent, struct Preset p) : QOpenGLWidget(parent), numObjects(0), lazyPopulateObjects(-1), hasBeenInitialized(false), bUseVSync(false), flowRightToLeft(false) { updateCount = 0; config = p; currentSelected = 0; centerPos.x = 0.f; centerPos.y = 0.f; centerPos.z = 1.f; centerPos.rot = 0.f; shadingTop = 0.8f; shadingBottom = 0.02f; reflectionUp = 0.f; reflectionBottom = 0.6f; setBackgroundColor(Qt::black); numObjects = 0; viewRotate = 0.f; viewRotateActive = 0; stepBackup = config.animationStep / config.animationSpeedUp; QSurfaceFormat f = format(); f.setSamples(4); f.setSwapInterval(0); // Detect if we should use OpenGL ES // Check if app-level ES is forced (via Qt::AA_UseOpenGLES) bool forceES = QCoreApplication::testAttribute(Qt::AA_UseOpenGLES); if (forceES) { // Use OpenGL ES 3.0 f.setRenderableType(QSurfaceFormat::OpenGLES); f.setVersion(3, 0); qDebug() << "YACReaderFlowGL: Requesting OpenGL ES 3.0 context"; } else { // Use Desktop OpenGL 3.3 Core f.setRenderableType(QSurfaceFormat::OpenGL); f.setVersion(3, 3); f.setProfile(QSurfaceFormat::CoreProfile); qDebug() << "YACReaderFlowGL: Requesting Desktop OpenGL 3.3 Core context"; } setFormat(f); timerId = startTimer(updateInterval); } void YACReaderFlowGL::timerEvent(QTimerEvent *event) { if (timerId == event->timerId()) update(); } void YACReaderFlowGL::startAnimationTimer() { if (timerId == -1) timerId = startTimer(updateInterval); } void YACReaderFlowGL::stopAnimationTimer() { if (timerId != -1) { killTimer(timerId); timerId = -1; } } YACReaderFlowGL::~YACReaderFlowGL() { makeCurrent(); delete vao; delete vbo; delete instanceVBO; delete shaderProgram; if (defaultTexture) { if (defaultTexture->isCreated()) defaultTexture->destroy(); delete defaultTexture; } #ifdef YACREADER_LIBRARY if (markTexture) { if (markTexture->isCreated()) markTexture->destroy(); delete markTexture; } if (readingTexture) { if (readingTexture->isCreated()) readingTexture->destroy(); delete readingTexture; } #endif doneCurrent(); } QSize YACReaderFlowGL::minimumSizeHint() const { return QSize(320, 200); } void YACReaderFlowGL::setupShaders() { bool isES = QOpenGLContext::currentContext()->isOpenGLES(); // Vertex Shader - Desktop GL 3.3 const char *vertexShaderSourceGL = R"( #version 330 core layout(location = 0) in vec3 position; layout(location = 1) in vec2 texCoord; layout(location = 2) in mat4 instanceModel; layout(location = 6) in vec4 instanceShading1; layout(location = 7) in float instanceOpacity; out vec2 vTexCoord; out vec4 vColor; uniform mat4 viewProjectionMatrix; void main() { gl_Position = viewProjectionMatrix * instanceModel * vec4(position, 1.0); vTexCoord = texCoord; float leftUpShading = instanceShading1.x; float leftDownShading = instanceShading1.y; float rightUpShading = instanceShading1.z; float rightDownShading = instanceShading1.w; float leftShading = mix(leftDownShading, leftUpShading, (position.y + 0.5)); float rightShading = mix(rightDownShading, rightUpShading, (position.y + 0.5)); float shading = mix(leftShading, rightShading, (position.x + 0.5)); vColor = vec4(shading * instanceOpacity); } )"; // Vertex Shader - OpenGL ES 3.0 const char *vertexShaderSourceES = R"( #version 300 es precision highp float; layout(location = 0) in vec3 position; layout(location = 1) in vec2 texCoord; layout(location = 2) in mat4 instanceModel; layout(location = 6) in vec4 instanceShading1; layout(location = 7) in float instanceOpacity; out vec2 vTexCoord; out vec4 vColor; uniform mat4 viewProjectionMatrix; void main() { gl_Position = viewProjectionMatrix * instanceModel * vec4(position, 1.0); vTexCoord = texCoord; float leftUpShading = instanceShading1.x; float leftDownShading = instanceShading1.y; float rightUpShading = instanceShading1.z; float rightDownShading = instanceShading1.w; float leftShading = mix(leftDownShading, leftUpShading, (position.y + 0.5)); float rightShading = mix(rightDownShading, rightUpShading, (position.y + 0.5)); float shading = mix(leftShading, rightShading, (position.x + 0.5)); vColor = vec4(shading * instanceOpacity); } )"; // Fragment Shader - Desktop GL 3.3 const char *fragmentShaderSourceGL = R"( #version 330 core in vec2 vTexCoord; in vec4 vColor; out vec4 fragColor; uniform sampler2D texture; uniform bool isReflection; uniform float reflectionUp; uniform float reflectionDown; uniform vec3 backgroundColor; uniform vec3 shadingColor; void main() { vec2 texCoord = vTexCoord; if (isReflection) { texCoord.y = 1.0 - vTexCoord.y; vec4 texColor = texture2D(texture, texCoord); float gradientFade = mix(1.0/3.0, reflectionUp / 2.0, vTexCoord.y); float shadingAmount = vColor.r * gradientFade; vec3 shadedColor = mix(backgroundColor, texColor.rgb, shadingAmount); fragColor = vec4(shadedColor, texColor.a); } else { vec4 texColor = texture2D(texture, texCoord); float shadingAmount = vColor.r; vec3 shadedColor = mix(backgroundColor, texColor.rgb, shadingAmount); fragColor = vec4(shadedColor, texColor.a); } } )"; // Fragment Shader - OpenGL ES 3.0 const char *fragmentShaderSourceES = R"( #version 300 es precision highp float; in vec2 vTexCoord; in vec4 vColor; out vec4 fragColor; uniform sampler2D texture; uniform bool isReflection; uniform float reflectionUp; uniform float reflectionDown; uniform vec3 backgroundColor; uniform vec3 shadingColor; void main() { vec2 texCoord = vTexCoord; if (isReflection) { texCoord.y = 1.0 - vTexCoord.y; vec4 texColor = texture(texture, texCoord); float gradientFade = mix(1.0/3.0, reflectionUp / 2.0, vTexCoord.y); float shadingAmount = vColor.r * gradientFade; vec3 shadedColor = mix(backgroundColor, texColor.rgb, shadingAmount); fragColor = vec4(shadedColor, texColor.a); } else { vec4 texColor = texture(texture, texCoord); float shadingAmount = vColor.r; vec3 shadedColor = mix(backgroundColor, texColor.rgb, shadingAmount); fragColor = vec4(shadedColor, texColor.a); } } )"; // Select shaders based on context type const char *vertexShader = isES ? vertexShaderSourceES : vertexShaderSourceGL; const char *fragmentShader = isES ? fragmentShaderSourceES : fragmentShaderSourceGL; qDebug() << "YACReaderFlowGL: Using" << (isES ? "OpenGL ES 3.0" : "Desktop OpenGL 3.3") << "shaders"; shaderProgram = new QOpenGLShaderProgram(this); shaderProgram->addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShader); shaderProgram->addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShader); if (!shaderProgram->link()) { qWarning() << "YACReaderFlowGL: Shader linking failed:" << shaderProgram->log(); } } void YACReaderFlowGL::setupGeometry() { // VAO for regular covers vao = new QOpenGLVertexArrayObject(this); vao->create(); vao->bind(); vbo = new QOpenGLBuffer(QOpenGLBuffer::VertexBuffer); vbo->create(); vbo->bind(); // Quad vertices with texture coordinates GLfloat vertices[] = { // Position (x, y, z), TexCoord (u, v) -0.5f, -0.5f, 0.0f, 0.0f, 1.0f, // Bottom-left 0.5f, -0.5f, 0.0f, 1.0f, 1.0f, // Bottom-right 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, // Top-right -0.5f, 0.5f, 0.0f, 0.0f, 0.0f // Top-left }; vbo->allocate(vertices, sizeof(vertices)); // Position attribute shaderProgram->enableAttributeArray(0); shaderProgram->setAttributeBuffer(0, GL_FLOAT, 0, 3, 5 * sizeof(GLfloat)); // TexCoord attribute shaderProgram->enableAttributeArray(1); shaderProgram->setAttributeBuffer(1, GL_FLOAT, 3 * sizeof(GLfloat), 2, 5 * sizeof(GLfloat)); // Create instance buffer (will be filled per-frame) instanceVBO = new QOpenGLBuffer(QOpenGLBuffer::VertexBuffer); instanceVBO->create(); instanceVBO->bind(); // Per-instance attributes (model matrix = 4 vec4s, shading = 1 vec4, opacity = 1 float) // Location 2-5: model matrix (mat4) for (int i = 0; i < 4; i++) { shaderProgram->enableAttributeArray(2 + i); shaderProgram->setAttributeBuffer(2 + i, GL_FLOAT, i * 4 * sizeof(GLfloat), 4, 21 * sizeof(GLfloat)); glVertexAttribDivisor(2 + i, 1); // Advance once per instance } // Location 6: shading vec4 (leftUp, leftDown, rightUp, rightDown) shaderProgram->enableAttributeArray(6); shaderProgram->setAttributeBuffer(6, GL_FLOAT, 16 * sizeof(GLfloat), 4, 21 * sizeof(GLfloat)); glVertexAttribDivisor(6, 1); // Location 7: opacity float shaderProgram->enableAttributeArray(7); shaderProgram->setAttributeBuffer(7, GL_FLOAT, 20 * sizeof(GLfloat), 1, 21 * sizeof(GLfloat)); glVertexAttribDivisor(7, 1); vao->release(); instanceVBO->release(); vbo->release(); } void YACReaderFlowGL::initializeGL() { if (!context() || !context()->isValid()) { qWarning() << "YACReaderFlowGL: Invalid OpenGL context"; return; } initializeOpenGLFunctions(); // Verify instancing support (available in OpenGL 3.3+ and ES 3.0+) bool hasInstancing = context()->hasExtension(QByteArrayLiteral("GL_ARB_instanced_arrays")) || context()->format().majorVersion() >= 3; if (!hasInstancing) { qWarning() << "YACReaderFlowGL: Instanced rendering not supported!"; qWarning() << "YACReaderFlowGL: OpenGL version:" << context()->format().majorVersion() << "." << context()->format().minorVersion(); return; } setupShaders(); setupGeometry(); defaultTexture = new QOpenGLTexture(QImage(":/images/defaultCover.png")); defaultTexture->setMinMagFilters(QOpenGLTexture::LinearMipMapLinear, QOpenGLTexture::LinearMipMapLinear); #ifdef YACREADER_LIBRARY markTexture = new QOpenGLTexture(QImage(":/images/readRibbon.png")); markTexture->setMinMagFilters(QOpenGLTexture::LinearMipMapLinear, QOpenGLTexture::LinearMipMapLinear); readingTexture = new QOpenGLTexture(QImage(":/images/readingRibbon.png")); readingTexture->setMinMagFilters(QOpenGLTexture::LinearMipMapLinear, QOpenGLTexture::LinearMipMapLinear); #endif if (lazyPopulateObjects != -1) populate(lazyPopulateObjects); hasBeenInitialized = true; } void YACReaderFlowGL::paintGL() { if (!context() || !context()->isValid() || !shaderProgram) return; QPainter painter; painter.begin(this); painter.beginNativePainting(); glEnable(GL_DEPTH_TEST); glEnable(GL_CULL_FACE); glEnable(GL_BLEND); glEnable(GL_MULTISAMPLE); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glClearColor(backgroundColor.redF(), backgroundColor.greenF(), backgroundColor.blueF(), 1.0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); if (numObjects > 0) { updatePositions(); updatePerspective(width(), height()); draw(); } glDisable(GL_MULTISAMPLE); glDisable(GL_BLEND); glDisable(GL_CULL_FACE); glDisable(GL_DEPTH_TEST); painter.endNativePainting(); QFont font = painter.font(); font.setFamily("Arial"); font.setPointSize(fontSize); painter.setFont(font); painter.setPen(textColor); painter.drawText(10, fontSize + 10, QString("%1/%2").arg(currentSelected + 1).arg(numObjects)); painter.end(); } void YACReaderFlowGL::resizeGL(int width, int height) { if (!context() || !context()->isValid()) return; fontSize = (width + height) * 0.010; if (fontSize < 10) fontSize = 10; updatePerspective(width, height); if (numObjects > 0) updatePositions(); } void YACReaderFlowGL::updatePerspective(int width, int height) { if (!context() || !context()->isValid()) return; float pixelRatio = devicePixelRatioF(); glViewport(0, 0, width * pixelRatio, height * pixelRatio); } void YACReaderFlowGL::calcPos(YACReader3DImage &image, int pos) { if (flowRightToLeft) { pos = pos * -1; } if (pos == 0) { image.current = centerPos; } else { if (pos > 0) { image.current.x = (config.centerDistance) + (config.xDistance * pos); image.current.y = config.yDistance * pos * -1; image.current.z = config.zDistance * pos * -1; image.current.rot = config.rotation; } else { image.current.x = (config.centerDistance) * -1 + (config.xDistance * pos); image.current.y = config.yDistance * pos; image.current.z = config.zDistance * pos; image.current.rot = config.rotation * -1; } } } void YACReaderFlowGL::calcVector(YACReader3DVector &vector, int pos) { calcPos(dummy, pos); vector.x = dummy.current.x; vector.y = dummy.current.y; vector.z = dummy.current.z; vector.rot = dummy.current.rot; } bool YACReaderFlowGL::animate(YACReader3DVector ¤tVector, YACReader3DVector &toVector) { float rotDiff = toVector.rot - currentVector.rot; float xDiff = toVector.x - currentVector.x; float yDiff = toVector.y - currentVector.y; float zDiff = toVector.z - currentVector.z; if (fabs(rotDiff) < 0.01 && fabs(xDiff) < 0.001 && fabs(yDiff) < 0.001 && fabs(zDiff) < 0.001) return true; currentVector.x = currentVector.x + (xDiff)*config.animationStep; currentVector.y = currentVector.y + (yDiff)*config.animationStep; currentVector.z = currentVector.z + (zDiff)*config.animationStep; if (fabs(rotDiff) > 0.01) { currentVector.rot = currentVector.rot + (rotDiff) * (config.animationStep * config.preRotation); } else { viewRotateActive = 0; } return false; } void YACReaderFlowGL::drawCover(const YACReader3DImage &image) { if (!shaderProgram || !vao || !image.texture) return; float w = image.width; float h = image.height; // Calculate opacity - original formula exactly float opacity = 1 - 1 / (config.animationFadeOutDist + config.viewRotateLightStrenght * fabs(viewRotate)) * fabs(0 - image.current.x); // Setup matrices QMatrix4x4 projectionMatrix; projectionMatrix.perspective(config.zoom, GLdouble(width()) / (float)height(), 1.0, 200.0); QMatrix4x4 viewMatrix; viewMatrix.translate(config.cfX, config.cfY, config.cfZ); viewMatrix.rotate(config.cfRX, 1, 0, 0); viewMatrix.rotate(viewRotate * config.viewAngle + config.cfRY, 0, 1, 0); viewMatrix.rotate(config.cfRZ, 0, 0, 1); QMatrix4x4 modelMatrix; modelMatrix.translate(image.current.x, image.current.y, image.current.z); modelMatrix.rotate(image.current.rot, 0, 1, 0); modelMatrix.translate(0.0f, -0.5f + h / 2.0f, 0.0f); modelMatrix.scale(w, h, 1.0f); QMatrix4x4 mvpMatrix = projectionMatrix * viewMatrix * modelMatrix; // Calculate per-corner shading exactly as original float LShading = ((config.rotation != 0) ? ((image.current.rot < 0) ? 1 - 1 / config.rotation * image.current.rot : 1) : 1); float RShading = ((config.rotation != 0) ? ((image.current.rot > 0) ? 1 - 1 / (config.rotation * -1) * image.current.rot : 1) : 1); float LUP = shadingTop + (1 - shadingTop) * LShading; float LDOWN = shadingBottom + (1 - shadingBottom) * LShading; float RUP = shadingTop + (1 - shadingTop) * RShading; float RDOWN = shadingBottom + (1 - shadingBottom) * RShading; // Bind shader and set uniforms shaderProgram->bind(); shaderProgram->setUniformValue("mvpMatrix", mvpMatrix); shaderProgram->setUniformValue("leftUpShading", LUP); shaderProgram->setUniformValue("leftDownShading", LDOWN); shaderProgram->setUniformValue("rightUpShading", RUP); shaderProgram->setUniformValue("rightDownShading", RDOWN); shaderProgram->setUniformValue("opacity", opacity); shaderProgram->setUniformValue("isReflection", false); // Bind texture and VAO image.texture->bind(); vao->bind(); // Draw cover glDrawArrays(GL_TRIANGLE_FAN, 0, 4); // Draw marks if needed if (showMarks && loaded[image.index] && marks[image.index] != Unread) { QOpenGLTexture *markTex = (marks[image.index] == Read) ? markTexture : readingTexture; QMatrix4x4 markModel; markModel.translate(image.current.x, image.current.y, image.current.z); markModel.rotate(image.current.rot, 0, 1, 0); float markWidth = 0.15f; float markHeight = 0.2f; float markCenterX = w / 2.0f - 0.125f; float markCenterY = -0.588f + h; markModel.translate(markCenterX, markCenterY, 0.001f); markModel.scale(markWidth, markHeight, 1.0f); QMatrix4x4 mvpMark = projectionMatrix * viewMatrix * markModel; shaderProgram->setUniformValue("mvpMatrix", mvpMark); shaderProgram->setUniformValue("leftUpShading", RUP * opacity); shaderProgram->setUniformValue("leftDownShading", RUP * opacity); shaderProgram->setUniformValue("rightUpShading", RUP * opacity); shaderProgram->setUniformValue("rightDownShading", RUP * opacity); shaderProgram->setUniformValue("opacity", 1.0f); shaderProgram->setUniformValue("isReflection", false); markTex->bind(); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); } vao->release(); shaderProgram->release(); } void YACReaderFlowGL::drawReflection(const YACReader3DImage &image) { if (!shaderProgram || !vao || !image.texture) return; float w = image.width; float h = image.height; // Calculate opacity - original formula exactly float opacity = 1 - 1 / (config.animationFadeOutDist + config.viewRotateLightStrenght * fabs(viewRotate)) * fabs(0 - image.current.x); // Setup matrices QMatrix4x4 projectionMatrix; projectionMatrix.perspective(config.zoom, GLdouble(width()) / (float)height(), 1.0, 200.0); QMatrix4x4 viewMatrix; viewMatrix.translate(config.cfX, config.cfY, config.cfZ); viewMatrix.rotate(config.cfRX, 1, 0, 0); viewMatrix.rotate(viewRotate * config.viewAngle + config.cfRY, 0, 1, 0); viewMatrix.rotate(config.cfRZ, 0, 0, 1); // Calculate per-corner shading exactly as original float LShading = ((config.rotation != 0) ? ((image.current.rot < 0) ? 1 - 1 / config.rotation * image.current.rot : 1) : 1); float RShading = ((config.rotation != 0) ? ((image.current.rot > 0) ? 1 - 1 / (config.rotation * -1) * image.current.rot : 1) : 1); float LUP = shadingTop + (1 - shadingTop) * LShading; float LDOWN = shadingBottom + (1 - shadingBottom) * LShading; float RUP = shadingTop + (1 - shadingTop) * RShading; float RDOWN = shadingBottom + (1 - shadingBottom) * RShading; // Draw reflection // In the old code, the reflection quad had vertices from y=-0.5-h (bottom) to y=-0.5 (top) // The OLD reflection shading was: // - Bottom corners (y = -0.5-h): LUP*opacity*reflectionUp/2, RUP*opacity*reflectionUp/2 // - Top corners (y = -0.5): LDOWN*opacity/3, RDOWN*opacity/3 // This means the reflection uses INVERTED vertical shading (LUP/RUP at bottom, LDOWN/RDOWN at top) // We need to pass swapped values to match this QMatrix4x4 reflectionMatrix; reflectionMatrix.translate(image.current.x, image.current.y, image.current.z); reflectionMatrix.rotate(image.current.rot, 0, 1, 0); reflectionMatrix.translate(0.0f, -0.5f - h / 2.0f, 0.0f); reflectionMatrix.scale(w, h, 1.0f); QMatrix4x4 mvpReflection = projectionMatrix * viewMatrix * reflectionMatrix; shaderProgram->bind(); shaderProgram->setUniformValue("mvpMatrix", mvpReflection); // Swap UP and DOWN for reflection to match old behavior shaderProgram->setUniformValue("leftUpShading", LDOWN); shaderProgram->setUniformValue("leftDownShading", LUP); shaderProgram->setUniformValue("rightUpShading", RDOWN); shaderProgram->setUniformValue("rightDownShading", RUP); shaderProgram->setUniformValue("opacity", opacity); shaderProgram->setUniformValue("isReflection", true); shaderProgram->setUniformValue("reflectionUp", reflectionUp); shaderProgram->setUniformValue("reflectionDown", reflectionBottom); image.texture->bind(); vao->bind(); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); vao->release(); shaderProgram->release(); } void YACReaderFlowGL::cleanupAnimation() { config.animationStep = stepBackup; viewRotateActive = 0; } void YACReaderFlowGL::draw() { if (!shaderProgram || !vao || numObjects == 0) return; // Calculate view-projection matrix once // Note: Old implementation used fixed 20.0 degrees FOV, not config.zoom QMatrix4x4 projectionMatrix; projectionMatrix.perspective(20.0, GLdouble(width()) / (float)height(), 1.0, 200.0); QMatrix4x4 viewMatrix; viewMatrix.translate(config.cfX, config.cfY, config.cfZ); viewMatrix.rotate(config.cfRX, 1, 0, 0); viewMatrix.rotate(viewRotate * config.viewAngle + config.cfRY, 0, 1, 0); viewMatrix.rotate(config.cfRZ, 0, 0, 1); QMatrix4x4 viewProjectionMatrix = projectionMatrix * viewMatrix; // Bind shader once shaderProgram->bind(); shaderProgram->setUniformValue("viewProjectionMatrix", viewProjectionMatrix); shaderProgram->setUniformValue("backgroundColor", QVector3D(backgroundColor.redF(), backgroundColor.greenF(), backgroundColor.blueF())); shaderProgram->setUniformValue("shadingColor", QVector3D(shadingColor.redF(), shadingColor.greenF(), shadingColor.blueF())); int CS = currentSelected; // Prepare instance data for all covers QVector instanceData; QVector drawOrder; // Build draw order (back to front) for (int count = numObjects - 1; count > -1; count--) { if (count > CS) { drawOrder.append(count); } } for (int count = 0; count < numObjects - 1; count++) { if (count < CS) { drawOrder.append(count); } } drawOrder.append(CS); // Draw reflections first shaderProgram->setUniformValue("isReflection", true); shaderProgram->setUniformValue("reflectionUp", reflectionUp); shaderProgram->setUniformValue("reflectionDown", reflectionBottom); for (int idx : drawOrder) { if (images[idx].texture) { prepareInstanceData(images[idx], true, instanceData); instanceVBO->bind(); instanceVBO->allocate(instanceData.data(), instanceData.size() * sizeof(GLfloat)); images[idx].texture->bind(); vao->bind(); glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, 1); instanceData.clear(); } } // Draw covers shaderProgram->setUniformValue("isReflection", false); for (int idx : drawOrder) { if (images[idx].texture) { prepareInstanceData(images[idx], false, instanceData); instanceVBO->bind(); instanceVBO->allocate(instanceData.data(), instanceData.size() * sizeof(GLfloat)); images[idx].texture->bind(); vao->bind(); glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, 1); // Draw marks if needed if (showMarks && loaded[images[idx].index] && marks[images[idx].index] != Unread) { drawMark(images[idx], viewProjectionMatrix); } instanceData.clear(); } } vao->release(); shaderProgram->release(); } void YACReaderFlowGL::prepareInstanceData(const YACReader3DImage &image, bool isReflection, QVector &data) { float w = image.width; float h = image.height; // Calculate opacity float opacity = 1 - 1 / (config.animationFadeOutDist + config.viewRotateLightStrenght * fabs(viewRotate)) * fabs(0 - image.current.x); // Calculate model matrix QMatrix4x4 modelMatrix; modelMatrix.translate(image.current.x, image.current.y, image.current.z); modelMatrix.rotate(image.current.rot, 0, 1, 0); if (isReflection) { modelMatrix.translate(0.0f, -0.5f - h / 2.0f, 0.0f); } else { modelMatrix.translate(0.0f, -0.5f + h / 2.0f, 0.0f); } modelMatrix.scale(w, h, 1.0f); // Calculate shading float LShading = ((config.rotation != 0) ? ((image.current.rot < 0) ? 1 - 1 / config.rotation * image.current.rot : 1) : 1); float RShading = ((config.rotation != 0) ? ((image.current.rot > 0) ? 1 - 1 / (config.rotation * -1) * image.current.rot : 1) : 1); float LUP = shadingTop + (1 - shadingTop) * LShading; float LDOWN = shadingBottom + (1 - shadingBottom) * LShading; float RUP = shadingTop + (1 - shadingTop) * RShading; float RDOWN = shadingBottom + (1 - shadingBottom) * RShading; // For reflection, swap vertical shading if (isReflection) { float temp = LUP; LUP = LDOWN; LDOWN = temp; temp = RUP; RUP = RDOWN; RDOWN = temp; } // Pack instance data: mat4 (16 floats) + vec4 shading (4 floats) + float opacity (1 float) = 21 floats const float *matData = modelMatrix.constData(); for (int i = 0; i < 16; i++) { data.append(matData[i]); } data.append(LUP); data.append(LDOWN); data.append(RUP); data.append(RDOWN); data.append(opacity); } void YACReaderFlowGL::drawMark(const YACReader3DImage &image, const QMatrix4x4 &viewProjectionMatrix) { QOpenGLTexture *markTex = (marks[image.index] == Read) ? markTexture : readingTexture; float w = image.width; float h = image.height; float opacity = 1 - 1 / (config.animationFadeOutDist + config.viewRotateLightStrenght * fabs(viewRotate)) * fabs(0 - image.current.x); float LShading = ((config.rotation != 0) ? ((image.current.rot < 0) ? 1 - 1 / config.rotation * image.current.rot : 1) : 1); float RShading = ((config.rotation != 0) ? ((image.current.rot > 0) ? 1 - 1 / (config.rotation * -1) * image.current.rot : 1) : 1); float RUP = shadingTop + (1 - shadingTop) * RShading; QMatrix4x4 markModel; markModel.translate(image.current.x, image.current.y, image.current.z); markModel.rotate(image.current.rot, 0, 1, 0); float markWidth = 0.15f; float markHeight = 0.2f; float markCenterX = w / 2.0f - 0.125f; float markCenterY = -0.588f + h; markModel.translate(markCenterX, markCenterY, 0.001f); markModel.scale(markWidth, markHeight, 1.0f); // Prepare instance data for mark QVector markData; const float *matData = markModel.constData(); for (int i = 0; i < 16; i++) { markData.append(matData[i]); } float shadingValue = RUP * opacity; markData.append(shadingValue); markData.append(shadingValue); markData.append(shadingValue); markData.append(shadingValue); markData.append(1.0f); instanceVBO->bind(); instanceVBO->allocate(markData.data(), markData.size() * sizeof(GLfloat)); markTex->bind(); vao->bind(); glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, 1); } void YACReaderFlowGL::showPrevious() { startAnimationTimer(); if (currentSelected > 0) { currentSelected--; emit centerIndexChanged(currentSelected); config.animationStep *= config.animationSpeedUp; if (config.animationStep > config.animationStepMax) { config.animationStep = config.animationStepMax; } if (viewRotateActive && viewRotate > -1) { viewRotate -= config.viewRotateAdd; } viewRotateActive = 1; } } void YACReaderFlowGL::showNext() { startAnimationTimer(); if (currentSelected < numObjects - 1) { currentSelected++; emit centerIndexChanged(currentSelected); config.animationStep *= config.animationSpeedUp; if (config.animationStep > config.animationStepMax) { config.animationStep = config.animationStepMax; } if (viewRotateActive && viewRotate < 1) { viewRotate += config.viewRotateAdd; } viewRotateActive = 1; } } void YACReaderFlowGL::setCurrentIndex(int pos) { if (!(pos >= 0 && pos < images.length() && images.length() > 0)) return; if (pos >= images.length() && images.length() > 0) pos = images.length() - 1; startAnimationTimer(); currentSelected = pos; config.animationStep *= config.animationSpeedUp; if (config.animationStep > config.animationStepMax) { config.animationStep = config.animationStepMax; } if (viewRotateActive && viewRotate < 1) { viewRotate += config.viewRotateAdd; } viewRotateActive = 1; } void YACReaderFlowGL::updatePositions() { int count; bool stopAnimation = true; for (count = numObjects - 1; count > -1; count--) { calcVector(images[count].animEnd, count - currentSelected); if (!animate(images[count].current, images[count].animEnd)) stopAnimation = false; } if (!viewRotateActive) { viewRotate += (0 - viewRotate) * config.viewRotateSub; } if (fabs(images[currentSelected].current.x - images[currentSelected].animEnd.x) < 1) { cleanupAnimation(); if (updateCount >= 0) { updateCount = 0; updateImageData(); } else updateCount++; } else updateCount++; if (stopAnimation) stopAnimationTimer(); } void YACReaderFlowGL::insert(char *name, QOpenGLTexture *texture, float x, float y, int item) { startAnimationTimer(); Q_UNUSED(name) if (item == -1) { images.push_back(YACReader3DImage()); item = numObjects; numObjects++; calcVector(images[item].current, item); images[item].current.z = images[item].current.z - 1; } images[item].texture = texture; images[item].width = x; images[item].height = y; images[item].index = item; } void YACReaderFlowGL::remove(int item) { if (item < 0 || item >= images.size()) return; startAnimationTimer(); loaded.remove(item); marks.remove(item); if (item <= currentSelected && currentSelected != 0) { currentSelected--; } QOpenGLTexture *texture = images[item].texture; int count = item; while (count <= numObjects - 1) { images[count].index--; count++; } images.removeAt(item); if (texture != defaultTexture) delete (texture); numObjects--; } void YACReaderFlowGL::add(int item) { float x = 1; float y = 1 * (700.f / 480.0f); QString s = "cover"; images.insert(item, YACReader3DImage()); loaded.insert(item, false); marks.insert(item, Unread); numObjects++; for (int i = item + 1; i < numObjects; i++) { images[i].index++; } insert(s.toLocal8Bit().data(), defaultTexture, x, y, item); } YACReader3DImage YACReaderFlowGL::getCurrentSelected() { return images[currentSelected]; } void YACReaderFlowGL::replace(char *name, QOpenGLTexture *texture, float x, float y, int item) { startAnimationTimer(); Q_UNUSED(name) if (images[item].index == item) { images[item].texture = texture; images[item].width = x; images[item].height = y; loaded[item] = true; } else loaded[item] = false; } void YACReaderFlowGL::populate(int n) { emit centerIndexChanged(0); float x = 1; float y = 1 * (700.f / 480.0f); int i; for (i = 0; i < n; i++) { QString s = "cover"; insert(s.toLocal8Bit().data(), defaultTexture, x, y); } loaded = QVector(n, false); } void YACReaderFlowGL::reset() { makeCurrent(); startAnimationTimer(); currentSelected = 0; loaded.clear(); for (int i = 0; i < numObjects; i++) { if (images[i].texture != defaultTexture) delete (images[i].texture); } numObjects = 0; images.clear(); if (!hasBeenInitialized) lazyPopulateObjects = -1; doneCurrent(); } void YACReaderFlowGL::reload() { startAnimationTimer(); int n = numObjects; reset(); populate(n); } // Slot implementations void YACReaderFlowGL::setCF_RX(int value) { startAnimationTimer(); config.cfRX = value; } void YACReaderFlowGL::setCF_RY(int value) { startAnimationTimer(); config.cfRY = value; } void YACReaderFlowGL::setCF_RZ(int value) { startAnimationTimer(); config.cfRZ = value; } void YACReaderFlowGL::setRotation(int angle) { startAnimationTimer(); config.rotation = -angle; } void YACReaderFlowGL::setX_Distance(int distance) { startAnimationTimer(); config.xDistance = distance / 100.0; } void YACReaderFlowGL::setCenter_Distance(int distance) { startAnimationTimer(); config.centerDistance = distance / 100.0; } void YACReaderFlowGL::setZ_Distance(int distance) { startAnimationTimer(); config.zDistance = distance / 100.0; } void YACReaderFlowGL::setCF_Y(int value) { startAnimationTimer(); config.cfY = value / 100.0; } void YACReaderFlowGL::setCF_Z(int value) { startAnimationTimer(); config.cfZ = value; } void YACReaderFlowGL::setY_Distance(int value) { startAnimationTimer(); config.yDistance = value / 100.0; } void YACReaderFlowGL::setFadeOutDist(int value) { startAnimationTimer(); config.animationFadeOutDist = value; } void YACReaderFlowGL::setLightStrenght(int value) { startAnimationTimer(); config.viewRotateLightStrenght = value; } void YACReaderFlowGL::setMaxAngle(int value) { startAnimationTimer(); config.viewAngle = value; } void YACReaderFlowGL::setPreset(const Preset &p) { startAnimationTimer(); config = p; } void YACReaderFlowGL::setZoom(int zoom) { startAnimationTimer(); config.zoom = zoom; } void YACReaderFlowGL::setPerformance(Performance performance) { if (this->performance != performance) { startAnimationTimer(); this->performance = performance; reload(); } } void YACReaderFlowGL::useVSync(bool b) { if (bUseVSync != b) { bUseVSync = b; QSurfaceFormat f = format(); f.setVersion(3, 3); f.setProfile(QSurfaceFormat::CoreProfile); f.setSwapInterval(b ? 1 : 0); setFormat(f); reset(); } } void YACReaderFlowGL::setShowMarks(bool value) { startAnimationTimer(); showMarks = value; } void YACReaderFlowGL::setMarks(QVector marks) { startAnimationTimer(); this->marks = marks; } void YACReaderFlowGL::setMarkImage(QImage &image) { Q_UNUSED(image); } void YACReaderFlowGL::markSlide(int index, YACReader::YACReaderComicReadStatus status) { startAnimationTimer(); marks[index] = status; } void YACReaderFlowGL::unmarkSlide(int index) { startAnimationTimer(); marks[index] = YACReader::Unread; } void YACReaderFlowGL::setSlideSize(QSize size) { Q_UNUSED(size); } void YACReaderFlowGL::clear() { reset(); } void YACReaderFlowGL::setCenterIndex(unsigned int index) { setCurrentIndex(index); } void YACReaderFlowGL::showSlide(int index) { setCurrentIndex(index); } int YACReaderFlowGL::centerIndex() { return currentSelected; } void YACReaderFlowGL::updateMarks() { } void YACReaderFlowGL::render() { } void YACReaderFlowGL::setFlowRightToLeft(bool b) { flowRightToLeft = b; } void YACReaderFlowGL::setBackgroundColor(const QColor &color) { backgroundColor = color; // Auto-calculate shadingColor based on background brightness qreal luminance = (backgroundColor.redF() * 0.299 + backgroundColor.greenF() * 0.587 + backgroundColor.blueF() * 0.114); if (luminance < 0.5) { // Dark background - shade towards white shadingColor = QColor(255, 255, 255); // Use original shading values for dark backgrounds shadingTop = 0.8f; shadingBottom = 0.02f; } else { // Light background - shade towards black shadingColor = QColor(0, 0, 0); // Adjust shading range for better contrast on light backgrounds shadingTop = 0.95f; shadingBottom = 0.3f; } update(); } void YACReaderFlowGL::setTextColor(const QColor &color) { textColor = color; update(); } void YACReaderFlowGL::setShadingColor(const QColor &color) { shadingColor = color; update(); } // Event handlers void YACReaderFlowGL::wheelEvent(QWheelEvent *event) { Movement m = getMovement(event); switch (m) { case None: return; case Forward: showNext(); break; case Backward: showPrevious(); break; default: break; } } void YACReaderFlowGL::keyPressEvent(QKeyEvent *event) { if ((event->key() == Qt::Key_Left && !flowRightToLeft) || (event->key() == Qt::Key_Right && flowRightToLeft)) { if (event->modifiers() == Qt::ControlModifier) setCurrentIndex((currentSelected - 10 < 0) ? 0 : currentSelected - 10); else showPrevious(); event->accept(); return; } if ((event->key() == Qt::Key_Right && !flowRightToLeft) || (event->key() == Qt::Key_Left && flowRightToLeft)) { if (event->modifiers() == Qt::ControlModifier) setCurrentIndex((currentSelected + 10 >= numObjects) ? numObjects - 1 : currentSelected + 10); else showNext(); event->accept(); return; } if (event->key() == Qt::Key_Up) { return; } event->ignore(); } void YACReaderFlowGL::mousePressEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton) { QVector3D intersection = getPlaneIntersection(event->x(), event->y(), images[currentSelected]); if ((intersection.x() > 0.5 && !flowRightToLeft) || (intersection.x() < -0.5 && flowRightToLeft)) { showNext(); } else if ((intersection.x() < -0.5 && !flowRightToLeft) || (intersection.x() > 0.5 && flowRightToLeft)) { showPrevious(); } } else { QOpenGLWidget::mousePressEvent(event); } doneCurrent(); } void YACReaderFlowGL::mouseDoubleClickEvent(QMouseEvent *event) { QVector3D intersection = getPlaneIntersection(event->x(), event->y(), images[currentSelected]); if (intersection.x() < 0.5 && intersection.x() > -0.5) { emit selected(centerIndex()); event->accept(); } } QVector3D YACReaderFlowGL::getPlaneIntersection(int x, int y, YACReader3DImage plane) { if (!context() || !context()->isValid()) return QVector3D(0, 0, 0); GLint viewport[4]; QMatrix4x4 m_modelview; QMatrix4x4 m_projection; makeCurrent(); glGetIntegerv(GL_VIEWPORT, viewport); m_projection.perspective(config.zoom, GLdouble(width()) / (float)height(), 1.0, 200.0); m_modelview.translate(config.cfX, config.cfY, config.cfZ); m_modelview.rotate(config.cfRX, 1, 0, 0); m_modelview.rotate(viewRotate * config.viewAngle + config.cfRY, 0, 1, 0); m_modelview.rotate(config.cfRZ, 0, 0, 1); m_modelview.translate(plane.current.x, plane.current.y, plane.current.z); m_modelview.rotate(plane.current.rot, 0, 1, 0); m_modelview.scale(plane.width, plane.height, 1.0f); doneCurrent(); QVector3D ray_origin(x * devicePixelRatioF(), y * devicePixelRatioF(), 0); QVector3D ray_end(x * devicePixelRatioF(), y * devicePixelRatioF(), 1.0); ray_origin = ray_origin.unproject(m_modelview, m_projection, QRect(viewport[0], viewport[1], viewport[2], viewport[3])); ray_end = ray_end.unproject(m_modelview, m_projection, QRect(viewport[0], viewport[1], viewport[2], viewport[3])); QVector3D ray_vector = ray_end - ray_origin; QVector3D plane_origin(-0.5f, -0.5f, 0); QVector3D plane_vektor_1 = QVector3D(0.5f, -0.5f, 0) - plane_origin; QVector3D plane_vektor_2 = QVector3D(-0.5f, 0.5f, 0) - plane_origin; double intersection_LES_determinant = ((plane_vektor_1.x() * plane_vektor_2.y() * (-1) * ray_vector.z()) + (plane_vektor_2.x() * (-1) * ray_vector.y() * plane_vektor_1.z()) + ((-1) * ray_vector.x() * plane_vektor_1.y() * plane_vektor_2.z()) - ((-1) * ray_vector.x() * plane_vektor_2.y() * plane_vektor_1.z()) - (plane_vektor_1.x() * (-1) * ray_vector.y() * plane_vektor_2.z()) - (plane_vektor_2.x() * plane_vektor_1.y() * (-1) * ray_vector.z())); QVector3D det = ray_origin - plane_origin; double intersection_ray_determinant = ((plane_vektor_1.x() * plane_vektor_2.y() * det.z()) + (plane_vektor_2.x() * det.y() * plane_vektor_1.z()) + (det.x() * plane_vektor_1.y() * plane_vektor_2.z()) - (det.x() * plane_vektor_2.y() * plane_vektor_1.z()) - (plane_vektor_1.x() * det.y() * plane_vektor_2.z()) - (plane_vektor_2.x() * plane_vektor_1.y() * det.z())); return ray_origin + ray_vector * (intersection_ray_determinant / intersection_LES_determinant); } // YACReaderComicFlowGL implementation YACReaderComicFlowGL::YACReaderComicFlowGL(QWidget *parent, struct Preset p) : YACReaderFlowGL(parent, p) { worker = new ImageLoaderGL(this); worker->flow = this; } void YACReaderComicFlowGL::setImagePaths(QStringList paths) { worker->reset(); reset(); numObjects = 0; if (lazyPopulateObjects != -1 || hasBeenInitialized) YACReaderFlowGL::populate(paths.size()); lazyPopulateObjects = paths.size(); this->paths = paths; } void YACReaderComicFlowGL::updateImageData() { if (worker->busy()) return; int idx = worker->index(); if (idx >= 0 && !worker->result().isNull()) { if (!loaded[idx]) { float x = 1; QImage img = worker->result(); QOpenGLTexture *texture = new QOpenGLTexture(img); if (performance == high || performance == ultraHigh) { texture->setAutoMipMapGenerationEnabled(true); texture->setMinMagFilters(QOpenGLTexture::LinearMipMapLinear, QOpenGLTexture::LinearMipMapLinear); } else { texture->setMinMagFilters(QOpenGLTexture::Linear, QOpenGLTexture::Linear); } float y = 1 * (float(img.height()) / img.width()); QString s = "cover"; replace(s.toLocal8Bit().data(), texture, x, y, idx); } } int count = 8; switch (performance) { case low: count = 8; break; case medium: count = 10; break; case high: count = 12; break; case ultraHigh: count = 16; break; } int *indexes = new int[2 * count + 1]; int center = currentSelected; indexes[0] = center; for (int j = 0; j < count; j++) { indexes[j * 2 + 1] = center + j + 1; indexes[j * 2 + 2] = center - j - 1; } for (int c = 0; c < 2 * count + 1; c++) { int i = indexes[c]; if ((i >= 0) && (i < numObjects)) if (!loaded[i]) { if (paths.size() > 0) { QString fname = paths.at(i); worker->generate(i, fname); } delete[] indexes; return; } } delete[] indexes; } void YACReaderComicFlowGL::remove(int item) { worker->lock(); worker->reset(); YACReaderFlowGL::remove(item); if (item >= 0 && item < paths.size()) { paths.removeAt(item); } worker->unlock(); } void YACReaderComicFlowGL::add(const QString &path, int index) { worker->lock(); worker->reset(); paths.insert(index, path); YACReaderFlowGL::add(index); worker->unlock(); } void YACReaderComicFlowGL::resortCovers(QList newOrder) { worker->lock(); worker->reset(); startAnimationTimer(); QList pathsNew; QVector loadedNew; QVector marksNew; QVector imagesNew; int index = 0; foreach (int i, newOrder) { if (i < 0 || i >= images.size()) { continue; } pathsNew << paths.at(i); loadedNew << loaded.at(i); marksNew << marks.at(i); imagesNew << images.at(i); imagesNew.last().index = index++; } paths = pathsNew; loaded = loadedNew; marks = marksNew; images = imagesNew; worker->unlock(); } // YACReaderPageFlowGL implementation YACReaderPageFlowGL::YACReaderPageFlowGL(QWidget *parent, struct Preset p) : YACReaderFlowGL(parent, p) { worker = new ImageLoaderByteArrayGL(this); worker->flow = this; } YACReaderPageFlowGL::~YACReaderPageFlowGL() { this->killTimer(timerId); rawImages.clear(); makeCurrent(); for (auto &image : images) { if (image.texture != defaultTexture) { if (image.texture->isCreated()) { image.texture->destroy(); } delete image.texture; } } if (defaultTexture != nullptr) { if (defaultTexture->isCreated()) { defaultTexture->destroy(); } delete defaultTexture; } doneCurrent(); } void YACReaderPageFlowGL::updateImageData() { if (worker->busy()) return; int idx = worker->index(); if (idx >= 0 && !worker->result().isNull()) { if (!loaded[idx]) { float x = 1; QImage img = worker->result(); QOpenGLTexture *texture = new QOpenGLTexture(img); if (performance == high || performance == ultraHigh) { texture->setAutoMipMapGenerationEnabled(true); texture->setMinMagFilters(QOpenGLTexture::LinearMipMapLinear, QOpenGLTexture::LinearMipMapLinear); } else { texture->setMinMagFilters(QOpenGLTexture::Linear, QOpenGLTexture::Linear); } float y = 1 * (float(img.height()) / img.width()); QString s = "cover"; replace(s.toLocal8Bit().data(), texture, x, y, idx); loaded[idx] = true; } } int count = 8; switch (performance) { case low: count = 8; break; case medium: count = 10; break; case high: count = 12; break; case ultraHigh: count = 14; break; } int *indexes = new int[2 * count + 1]; int center = currentSelected; indexes[0] = center; for (int j = 0; j < count; j++) { indexes[j * 2 + 1] = center + j + 1; indexes[j * 2 + 2] = center - j - 1; } for (int c = 0; c < 2 * count + 1; c++) { int i = indexes[c]; if ((i >= 0) && (i < numObjects)) if (rawImages.size() > 0) if (!loaded[i] && imagesReady[i]) { worker->generate(i, rawImages.at(i)); delete[] indexes; return; } } delete[] indexes; } void YACReaderPageFlowGL::populate(int n) { worker->reset(); if (lazyPopulateObjects != -1 || hasBeenInitialized) YACReaderFlowGL::populate(n); lazyPopulateObjects = n; imagesReady = QVector(n, false); rawImages = QVector(n); imagesSetted = QVector(n, false); } // ImageLoaderGL implementation QImage ImageLoaderGL::loadImage(const QString &fileName) { QImage image; if (!image.load(fileName)) { return QImage(); } switch (flow->performance) { case low: image = image.scaledToWidth(200, Qt::SmoothTransformation); break; case medium: image = image.scaledToWidth(256, Qt::SmoothTransformation); break; case high: image = image.scaledToWidth(320, Qt::SmoothTransformation); break; case ultraHigh: break; } return image; } ImageLoaderGL::ImageLoaderGL(YACReaderFlowGL *flow) : QThread(), flow(flow), restart(false), working(false), idx(-1) { } ImageLoaderGL::~ImageLoaderGL() { mutex.lock(); condition.wakeOne(); mutex.unlock(); wait(); } bool ImageLoaderGL::busy() const { return isRunning() ? working : false; } void ImageLoaderGL::generate(int index, const QString &fileName) { mutex.lock(); this->idx = index; this->fileName = fileName; this->size = size; this->img = QImage(); mutex.unlock(); if (!isRunning()) start(); else { restart = true; condition.wakeOne(); } } void ImageLoaderGL::lock() { mutex.lock(); } void ImageLoaderGL::unlock() { mutex.unlock(); } void ImageLoaderGL::run() { for (;;) { mutex.lock(); this->working = true; QString fileName = this->fileName; mutex.unlock(); QImage image = loadImage(fileName); mutex.lock(); this->working = false; this->img = image; mutex.unlock(); mutex.lock(); if (!this->restart) condition.wait(&mutex); restart = false; mutex.unlock(); } } QImage ImageLoaderGL::result() { return img; } // ImageLoaderByteArrayGL implementation QImage ImageLoaderByteArrayGL::loadImage(const QByteArray &raw) { QImage image; if (!image.loadFromData(raw)) { return QImage(); } switch (flow->performance) { case low: image = image.scaledToWidth(128, Qt::SmoothTransformation); break; case medium: image = image.scaledToWidth(196, Qt::SmoothTransformation); break; case high: image = image.scaledToWidth(256, Qt::SmoothTransformation); break; case ultraHigh: image = image.scaledToWidth(320, Qt::SmoothTransformation); break; } return image; } ImageLoaderByteArrayGL::ImageLoaderByteArrayGL(YACReaderFlowGL *flow) : QThread(), flow(flow), restart(false), working(false), idx(-1) { } ImageLoaderByteArrayGL::~ImageLoaderByteArrayGL() { mutex.lock(); condition.wakeOne(); mutex.unlock(); wait(); } bool ImageLoaderByteArrayGL::busy() const { return isRunning() ? working : false; } void ImageLoaderByteArrayGL::generate(int index, const QByteArray &raw) { mutex.lock(); this->idx = index; this->rawData = raw; this->size = size; this->img = QImage(); mutex.unlock(); if (!isRunning()) start(); else { restart = true; condition.wakeOne(); } } void ImageLoaderByteArrayGL::run() { for (;;) { mutex.lock(); this->working = true; QByteArray raw = this->rawData; mutex.unlock(); QImage image = loadImage(raw); mutex.lock(); this->working = false; this->img = image; mutex.unlock(); mutex.lock(); if (!this->restart) condition.wait(&mutex); restart = false; mutex.unlock(); } } QImage ImageLoaderByteArrayGL::result() { return img; }