Replace custom Image buffers with QImage

Texture decoding and generated atlases previously used a custom packed-pixel
container alongside QImage, duplicating storage and image-processing code.
Use QImage throughout texture preparation and upload, remove Image and its
pixel-format enum, and store font and string masks in Alpha8 atlases.

Preserve power-of-two dimensions with smooth scaling and keep the existing
OpenGL formats, shaders, and CPU mipmap filtering. Handle padded scanlines,
restore the caller's unpack alignment on every upload return, and centralize
binding cleanup so failed uploads do not leave stale texture caches.

Validation: full application build and diff checks passed. OpenGL readback
checks covered RGB/RGBA/alpha data, padded and custom strides, generated and
supplied mipmaps, unpack-state restoration, and failure cleanup. All nine
grid mip levels matched the previous generator. Visual rendering was checked
by the user.
This commit is contained in:
Leonardo Zide committed 2026-10-12 19:24:44 -07:00
1 parent 05ba97dd88
commit b69db29428
19 files changed
+204 -353

No files matched your search

-201
View File
@@ -1,201 +0,0 @@
#include "lc_global.h"
#include "image.h"
#include "lc_file.h"
static void CopyFromQImage(const QImage& Src, Image& Dest)
{
const bool Alpha = Src.hasAlphaChannel();
Dest.Allocate(Src.width(), Src.height(), Alpha ? lcPixelFormat::R8G8B8A8 : lcPixelFormat::R8G8B8);
quint8* Bytes = (quint8*)Dest.mData;
for (int y = 0; y < Dest.mHeight; y++)
{
for (int x = 0; x < Dest.mWidth; x++)
{
QRgb Pixel = Src.pixel(x, y);
*Bytes++ = qRed(Pixel);
*Bytes++ = qGreen(Pixel);
*Bytes++ = qBlue(Pixel);
if (Alpha)
*Bytes++ = qAlpha(Pixel);
}
}
}
Image::Image()
{
mData = nullptr;
mWidth = 0;
mHeight = 0;
mFormat = lcPixelFormat::Invalid;
}
Image::Image(Image&& Other)
{
mData = Other.mData;
mWidth = Other.mWidth;
mHeight = Other.mHeight;
mFormat = Other.mFormat;
Other.mData = nullptr;
Other.mWidth = 0;
Other.mHeight = 0;
Other.mFormat = lcPixelFormat::Invalid;
}
Image::~Image()
{
FreeData();
}
int Image::GetBPP() const
{
switch (mFormat)
{
case lcPixelFormat::Invalid:
return 0;
case lcPixelFormat::A8:
return 1;
case lcPixelFormat::L8A8:
return 2;
case lcPixelFormat::R8G8B8:
return 3;
case lcPixelFormat::R8G8B8A8:
return 4;
}
return 0;
}
bool Image::HasAlpha() const
{
switch (mFormat)
{
case lcPixelFormat::Invalid:
return false;
case lcPixelFormat::A8:
return true;
case lcPixelFormat::L8A8:
return true;
case lcPixelFormat::R8G8B8:
return false;
case lcPixelFormat::R8G8B8A8:
return true;
}
return 0;
}
void Image::FreeData()
{
free(mData);
mData = nullptr;
mWidth = 0;
mHeight = 0;
mFormat = lcPixelFormat::Invalid;
}
void Image::Allocate(int Width, int Height, lcPixelFormat Format)
{
FreeData();
mWidth = Width;
mHeight = Height;
mFormat = Format;
mData = (unsigned char*)malloc(static_cast<size_t>(mWidth) * mHeight * GetBPP());
}
void Image::ResizePow2()
{
int i, shifted_x, shifted_y;
shifted_x = mWidth;
for (i = 0; ((i < 16) && (shifted_x != 0)); i++)
shifted_x = shifted_x >> 1;
shifted_x = (i != 0) ? 1 << (i-1) : 1;
shifted_y = mHeight;
for (i = 0; ((i < 16) && (shifted_y != 0)); i++)
shifted_y = shifted_y >> 1;
shifted_y = (i != 0) ? 1 << (i-1) : 1;
if ((shifted_x != mWidth) || (shifted_y != mHeight))
Resize (shifted_x, shifted_y);
}
void Image::Resize(int width, int height)
{
int i, j, k, components, stx, sty;
float accumx, accumy;
unsigned char* bits = nullptr;
components = GetBPP();
const size_t BufferSize = static_cast<size_t>(width) * height * components;
if (BufferSize)
{
bits = (unsigned char*)malloc(BufferSize);
if (bits)
{
for (j = 0; j < mHeight; j++)
{
accumy = (float)height*j / (float)mHeight;
sty = (int)floor(accumy);
for (i = 0; i < mWidth; i++)
{
accumx = (float)width*i / (float)mWidth;
stx = (int)floor(accumx);
for (k = 0; k < components; k++)
bits[(stx + sty*width)*components + k] = mData[(i + j * mWidth) * components + k];
}
}
}
}
free(mData);
mData = bits;
mWidth = width;
mHeight = height;
}
bool Image::FileLoad(lcFile& File)
{
QImage Image;
if (lcMemFile* MemoryFile = dynamic_cast<lcMemFile*>(&File))
{
if (!MemoryFile->mBuffer || MemoryFile->mPosition >= MemoryFile->mFileSize)
return false;
const size_t Length = MemoryFile->mFileSize - MemoryFile->mPosition;
if (Length > static_cast<size_t>(std::numeric_limits<int>::max()) || !Image.loadFromData(MemoryFile->mBuffer + MemoryFile->mPosition, static_cast<int>(Length)))
return false;
}
else if (lcDiskFile* DiskFile = dynamic_cast<lcDiskFile*>(&File))
{
QFile& Device = DiskFile->GetFile();
const qint64 Position = Device.pos();
if (Position < 0)
return false;
const bool Loaded = Image.load(&Device, nullptr);
const bool Restored = Device.seek(Position);
if (!Loaded || !Restored)
return false;
}
else
return false;
CopyFromQImage(Image, *this);
return true;
}
-43
View File
@@ -1,43 +0,0 @@
#pragma once
// Image Options
#define LC_IMAGE_TRANSPARENT 0x2000
//#define LC_IMAGE_MASK 0x7000
enum class lcPixelFormat
{
Invalid,
A8,
L8A8,
R8G8B8,
R8G8B8A8
};
class Image
{
public:
Image();
Image(Image&& Other);
~Image();
Image(const Image&) = delete;
Image& operator=(const Image&) = delete;
Image& operator=(Image&&) = delete;
int GetBPP() const;
bool HasAlpha() const;
// Reads disk or memory files from the current position, preserving the cursor.
bool FileLoad(lcFile& File);
void Resize(int Width, int Height);
void ResizePow2();
void Allocate(int Width, int Height, lcPixelFormat Format);
void FreeData();
int mWidth;
int mHeight;
lcPixelFormat mFormat;
unsigned char* mData;
};
+6 -3
View File
@@ -972,10 +972,13 @@ bool lcAssetLoader::ProcessCompletions()
if (Stale)
mLibrary->ReleaseTexture(Texture);
else if (Completed.TextureResult.DecodedImage)
else if (!Completed.TextureResult.DecodedImage.isNull())
{
Texture->AdoptDecodedImage(std::move(*Completed.TextureResult.DecodedImage), LC_TEXTURE_MIPMAPS);
UploadTextureRequest(RequestedTexture);
Texture->AdoptDecodedImage(std::move(Completed.TextureResult.DecodedImage), LC_TEXTURE_MIPMAPS);
if (Texture->NeedsUpload())
UploadTextureRequest(RequestedTexture);
else
FinishTextureRequest(RequestedTexture);
}
else
{
-1
View File
@@ -10,7 +10,6 @@ class lcMesh;
class lcSynthInfo;
class lcMemFile;
class lcLibraryMeshData;
class Image;
class lcTexture;
enum class lcTextureLoadError;
+54 -25
View File
@@ -999,19 +999,43 @@ void lcContext::ClearTexture2D()
void lcContext::ClearTextureCubeMap()
{
if (mTexture2D == 0)
if (mTextureCubeMap == 0)
return;
glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
mTextureCubeMap = 0;
}
void lcContext::UploadTextureImage(GLenum Target, int Level, GLenum Format, const QImage& Image)
{
const size_t RowBytes = static_cast<size_t>(Image.width()) * (Image.depth() / 8);
const size_t AlignedRowBytes = (RowBytes + 3) & ~size_t(3);
if (static_cast<size_t>(Image.bytesPerLine()) == AlignedRowBytes)
{
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
glTexImage2D(Target, Level, Format, Image.width(), Image.height(), 0, Format, GL_UNSIGNED_BYTE, Image.constBits());
}
else
{
std::vector<quint8> PackedPixels(RowBytes * Image.height());
for (int Row = 0; Row < Image.height(); Row++)
memcpy(PackedPixels.data() + Row * RowBytes, Image.constScanLine(Row), RowBytes);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexImage2D(Target, Level, Format, Image.width(), Image.height(), 0, Format, GL_UNSIGNED_BYTE, PackedPixels.data());
}
}
bool lcContext::UploadTexture(lcTexture* Texture)
{
for (int Error = 0; Error < 16 && glGetError() != GL_NO_ERROR; Error++)
{
}
GLint UnpackAlignment;
glGetIntegerv(GL_UNPACK_ALIGNMENT, &UnpackAlignment);
if (!Texture->mTexture)
glGenTextures(1, &Texture->mTexture);
@@ -1026,6 +1050,19 @@ bool lcContext::UploadTexture(lcTexture* Texture)
const int FilterIndex = FilterFlags >> LC_TEXTURE_FILTER_SHIFT;
const int MipIndex = Flags & LC_TEXTURE_MIPMAPS ? 0 : 1;
const auto FinishUpload = [this, Flags, UnpackAlignment](bool Success)
{
if ((Flags & LC_TEXTURE_CUBEMAP) == 0)
ClearTexture2D();
else
ClearTextureCubeMap();
glPixelStorei(GL_UNPACK_ALIGNMENT, UnpackAlignment);
const bool NoError = glGetError() == GL_NO_ERROR;
return Success && NoError;
};
unsigned int Faces, Target;
if ((Flags & LC_TEXTURE_CUBEMAP) == 0)
@@ -1045,7 +1082,6 @@ bool lcContext::UploadTexture(lcTexture* Texture)
glTexParameteri(Target, GL_TEXTURE_WRAP_T, (Flags & LC_TEXTURE_WRAPV) ? GL_REPEAT : GL_CLAMP_TO_EDGE);
glTexParameteri(Target, GL_TEXTURE_MIN_FILTER, Filters[MipIndex][FilterIndex]);
glTexParameteri(Target, GL_TEXTURE_MAG_FILTER, Filters[1][FilterIndex]);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
if (Flags & LC_TEXTURE_CUBEMAP)
{
@@ -1058,22 +1094,17 @@ bool lcContext::UploadTexture(lcTexture* Texture)
glTexParameterf(Target, GL_TEXTURE_MAX_ANISOTROPY_EXT, lcMin(4.0f, gMaxAnisotropy));
int Format;
switch (Texture->GetImage(0).mFormat)
switch (Texture->GetImage(0).format())
{
default:
case lcPixelFormat::Invalid:
Format = 0;
break;
case lcPixelFormat::A8:
return FinishUpload(false);
case QImage::Format_Alpha8:
Format = GL_ALPHA;
break;
case lcPixelFormat::L8A8:
Format = GL_LUMINANCE_ALPHA;
break;
case lcPixelFormat::R8G8B8:
case QImage::Format_RGB888:
Format = GL_RGB;
break;
case lcPixelFormat::R8G8B8A8:
case QImage::Format_RGBA8888:
Format = GL_RGBA;
break;
}
@@ -1087,15 +1118,15 @@ bool lcContext::UploadTexture(lcTexture* Texture)
for (size_t FaceIdx = 0; FaceIdx < Faces; FaceIdx++)
{
const void* ImageData = Texture->GetImage(CurrentImage).mData;
const QImage& Image = Texture->GetImage(CurrentImage);
glTexImage2D(Target, 0, Format, Texture->mWidth, Texture->mHeight, 0, Format, GL_UNSIGNED_BYTE, ImageData);
UploadTextureImage(Target, 0, Format, Image);
if (LoadMipmaps)
{
int Width = Texture->mWidth;
int Height = Texture->mHeight;
int Components = Texture->GetImage(CurrentImage).GetBPP();
int Components = Image.depth() / 8;
GLubyte* MipBuffer = nullptr;
if (!HasMipmaps)
@@ -1103,9 +1134,11 @@ bool lcContext::UploadTexture(lcTexture* Texture)
MipBuffer = new GLubyte[Width * Height * Components];
if (!MipBuffer)
return false;
return FinishUpload(false);
memcpy(MipBuffer, ImageData, Width * Height * Components);
const size_t RowBytes = static_cast<size_t>(Width) * Components;
for (int Row = 0; Row < Height; Row++)
memcpy(MipBuffer + Row * RowBytes, Image.constScanLine(Row), RowBytes);
}
for (int Level = 1; ((Width != 1) || (Height != 1)); Level++)
@@ -1142,13 +1175,14 @@ bool lcContext::UploadTexture(lcTexture* Texture)
}
}
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexImage2D(Target, Level, Format, Width, Height, 0, Format, GL_UNSIGNED_BYTE, MipBuffer);
}
else
{
const void* Data = Texture->GetImage(++CurrentImage).mData;
const QImage& MipImage = Texture->GetImage(++CurrentImage);
glTexImage2D(Target, Level, Format, Width, Height, 0, Format, GL_UNSIGNED_BYTE, Data);
UploadTextureImage(Target, Level, Format, MipImage);
}
}
@@ -1163,12 +1197,7 @@ bool lcContext::UploadTexture(lcTexture* Texture)
Target++;
}
if ((Flags & LC_TEXTURE_CUBEMAP) == 0)
ClearTexture2D();
else
ClearTextureCubeMap();
return glGetError() == GL_NO_ERROR;
return FinishUpload(true);
}
void lcContext::SetColor(float Red, float Green, float Blue, float Alpha)
+1
View File
@@ -282,6 +282,7 @@ protected:
static void DestroyOffscreenContext();
lcResult<void> CreateShaderPrograms();
void UploadTextureImage(GLenum Target, int Level, GLenum Format, const QImage& Image);
void FlushState();
bool TryShareDepthAttachment(GLuint SourceFramebuffer, QOpenGLFramebufferObject& TargetFramebuffer);
+1 -1
View File
@@ -701,7 +701,7 @@ lcTextureBuildResult lcPiecesLibrary::BuildTextureData(const lcFileSource& Sourc
lcTextureBuildResult Result = mTextureManager->BuildTextureData(*File.value());
if (!Result.DecodedImage && Source.EmbeddedData)
if (Result.DecodedImage.isNull() && Source.EmbeddedData)
Result.ErrorDetails = tr("Could not decode embedded texture '%1'.").arg(Source.EmbeddedData->FileName);
return Result;
-1
View File
@@ -1,7 +1,6 @@
#include "lc_global.h"
#include "lc_profile.h"
#include "lc_context.h"
#include "image.h"
#include "lc_model.h"
#include "project.h"
#include "lc_viewsphere.h"
+2
View File
@@ -1,5 +1,7 @@
#pragma once
#define LC_IMAGE_TRANSPARENT 0x2000
enum class lcShadingMode;
enum class lcSelectionRenderMode;
enum class lcAxisIconLocation;
+7 -9
View File
@@ -44,14 +44,13 @@ void lcStringCache::CacheStrings(const QStringList& Strings)
if (!Update)
return;
Image TextureImage;
TextureImage.Allocate(256, 256, lcPixelFormat::L8A8);
QImage TextureImage(256, 256, QImage::Format_Alpha8);
QImage Image(128, 128, QImage::Format_ARGB32);
QPainter Painter;
QFont Font("Helvetica", 20);
int DestX = 0, DestY = 0, DestHeight = 0;
memset(TextureImage.mData, 0, TextureImage.mWidth * TextureImage.mHeight * 2);
TextureImage.fill(0);
for (auto& Entry : mStrings)
{
@@ -65,7 +64,7 @@ void lcStringCache::CacheStrings(const QStringList& Strings)
Painter.drawText(0, 0, Image.width(), Image.height(), 0, Entry.first, &SourceRect);
Painter.end();
if (DestX + SourceRect.width() + 2 > TextureImage.mWidth)
if (DestX + SourceRect.width() + 2 > TextureImage.width())
{
DestX = 0;
DestY += DestHeight + 2;
@@ -74,25 +73,24 @@ void lcStringCache::CacheStrings(const QStringList& Strings)
lcStringCacheEntry& String = Entry.second;
if (SourceRect.width() + 2 > TextureImage.mWidth || DestY + SourceRect.height() + 2 > TextureImage.mHeight || DestY + SourceRect.height() + 2 > TextureImage.mHeight)
if (SourceRect.width() + 2 > TextureImage.width() || DestY + SourceRect.height() + 2 > TextureImage.height() || DestY + SourceRect.height() + 2 > TextureImage.height())
{
memset(&String, 0, sizeof(String));
break;
}
String.Top = TextureImage.mHeight - DestY - 2;
String.Top = TextureImage.height() - DestY - 2;
String.Bottom = String.Top - SourceRect.height() + 1;
String.Left = DestX + 1;
String.Right = String.Left + SourceRect.width() - 2;
for (int y = SourceRect.top(); y < SourceRect.bottom(); y++)
{
unsigned char* Dest = TextureImage.mData + ((String.Top - y) * TextureImage.mWidth + String.Left) * 2;
unsigned char* Dest = TextureImage.scanLine(String.Top - y) + String.Left;
for (int x = SourceRect.left(); x < SourceRect.right(); x++)
{
*Dest = *(Dest + 1) = qRed(Image.pixel(x, y));
Dest += 2;
*Dest++ = qRed(Image.pixel(x, y));
}
}
+69 -35
View File
@@ -1,7 +1,6 @@
#include "lc_global.h"
#include "lc_texture.h"
#include "lc_context.h"
#include "image.h"
#include "lc_glextensions.h"
lcTexture* gGridTexture;
@@ -20,45 +19,43 @@ void lcTexture::CreateGridTexture()
{
constexpr int NumLevels = 9;
mImages.resize(NumLevels);
quint8* Previous = nullptr;
const QImage* Previous = nullptr;
for (int ImageLevel = 0; ImageLevel < NumLevels; ImageLevel++)
{
Image& GridImage = mImages[ImageLevel];
QImage& GridImage = mImages[ImageLevel];
const int GridSize = 256 >> ImageLevel;
GridImage.Allocate(GridSize, GridSize, lcPixelFormat::A8);
GridImage = QImage(GridSize, GridSize, QImage::Format_Alpha8);
if (Previous)
{
const int PreviousGridSize = 2 * GridSize;
for (int y = 0; y < GridSize - 1; y++)
{
for (int x = 0; x < GridSize - 1; x++)
{
const quint8 a = Previous[x * 2 + y * 2 * PreviousGridSize] > 64 ? 255 : 0;
const quint8 b = Previous[x * 2 + 1 + y * 2 * PreviousGridSize] > 64 ? 255 : 0;
const quint8 c = Previous[x * 2 + (y * 2 + 1) * PreviousGridSize] > 64 ? 255 : 0;
const quint8 d = Previous[x * 2 + 1 + (y * 2 + 1) * PreviousGridSize] > 64 ? 255 : 0;
GridImage.mData[x + y * GridSize] = (a + b + c + d) / 4;
const quint8 a = Previous->constScanLine(y * 2)[x * 2] > 64 ? 255 : 0;
const quint8 b = Previous->constScanLine(y * 2)[x * 2 + 1] > 64 ? 255 : 0;
const quint8 c = Previous->constScanLine(y * 2 + 1)[x * 2] > 64 ? 255 : 0;
const quint8 d = Previous->constScanLine(y * 2 + 1)[x * 2 + 1] > 64 ? 255 : 0;
GridImage.scanLine(y)[x] = (a + b + c + d) / 4;
}
int x = GridSize - 1;
const quint8 a = Previous[x * 2 + y * 2 * PreviousGridSize];
const quint8 c = Previous[x * 2 + (y * 2 + 1) * PreviousGridSize];
GridImage.mData[x + y * GridSize] = (a + c) / 2;
const quint8 a = Previous->constScanLine(y * 2)[x * 2];
const quint8 c = Previous->constScanLine(y * 2 + 1)[x * 2];
GridImage.scanLine(y)[x] = (a + c) / 2;
}
int y = GridSize - 1;
for (int x = 0; x < GridSize - 1; x++)
{
const quint8 a = Previous[x * 2 + y * 2 * PreviousGridSize];
const quint8 b = Previous[x * 2 + 1 + y * 2 * PreviousGridSize];
GridImage.mData[x + y * GridSize] = (a + b) / 2;
const quint8 a = Previous->constScanLine(y * 2)[x * 2];
const quint8 b = Previous->constScanLine(y * 2)[x * 2 + 1];
GridImage.scanLine(y)[x] = (a + b) / 2;
}
int x = GridSize - 1;
GridImage.mData[x + y * GridSize] = Previous[x + y * PreviousGridSize];
GridImage.scanLine(y)[x] = Previous->constScanLine(y)[x];
}
else
{
@@ -104,13 +101,13 @@ void lcTexture::CreateGridTexture()
const quint8 b = TempBuffer[x + 1 + y * GridSize];
const quint8 c = TempBuffer[x + (y + 1) * GridSize];
const quint8 d = TempBuffer[x + 1 + (y + 1) * GridSize];
GridImage.mData[x + y * GridSize] = (a + b + c + d) / 4;
GridImage.scanLine(y)[x] = (a + b + c + d) / 4;
}
int x = GridSize - 1;
const quint8 a = TempBuffer[x + y * GridSize];
const quint8 c = TempBuffer[x + (y + 1) * GridSize];
GridImage.mData[x + y * GridSize] = (a + c) / 2;
GridImage.scanLine(y)[x] = (a + c) / 2;
}
int y = GridSize - 1;
@@ -118,15 +115,15 @@ void lcTexture::CreateGridTexture()
{
const quint8 a = TempBuffer[x + y * GridSize];
const quint8 b = TempBuffer[x + 1 + y * GridSize];
GridImage.mData[x + y * GridSize] = (a + b) / 2;
GridImage.scanLine(y)[x] = (a + b) / 2;
}
int x = GridSize - 1;
GridImage.mData[x + y * GridSize] = TempBuffer[x + y * GridSize];
GridImage.scanLine(y)[x] = TempBuffer[x + y * GridSize];
delete[] TempBuffer;
}
Previous = GridImage.mData;
Previous = &GridImage;
}
mRefCount = 1;
@@ -138,19 +135,48 @@ void lcTexture::CreateGridTexture()
Upload(Context);
}
void lcTexture::SetImage(Image&& Image, int Flags)
QImage lcTexture::PrepareImage(QImage Image)
{
mImages.clear();
mImages.emplace_back(std::move(Image));
mFlags = Flags;
if (Image.isNull())
return QImage();
LoadImages();
QImage::Format Format = QImage::Format_Alpha8;
if (Image.format() != QImage::Format_Alpha8)
Format = Image.hasAlphaChannel() ? QImage::Format_RGBA8888 : QImage::Format_RGB888;
int Width = 1;
int Height = 1;
while (Width <= Image.width() / 2 && Width < 32768)
Width *= 2;
while (Height <= Image.height() / 2 && Height < 32768)
Height *= 2;
if (Width != Image.width() || Height != Image.height())
Image = Image.scaled(Width, Height, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
return Image.convertToFormat(Format);
}
void lcTexture::AdoptDecodedImage(Image&& Image, int Flags)
void lcTexture::SetImage(QImage SourceImage, int Flags)
{
AdoptDecodedImage(std::move(SourceImage), Flags);
if (mState == lcTextureState::Decoded)
LoadImages();
}
void lcTexture::AdoptDecodedImage(QImage SourceImage, int Flags)
{
QImage TextureImage = PrepareImage(std::move(SourceImage));
if (TextureImage.isNull())
{
SetFailed(lcTextureLoadError::DecodeFailed, QString());
return;
}
mImages.clear();
mImages.emplace_back(std::move(Image));
mImages.emplace_back(std::move(TextureImage));
mFlags = Flags;
mState = lcTextureState::Decoded;
}
@@ -167,7 +193,7 @@ void lcTexture::SetFailed(lcTextureLoadError Error, QString Details)
mLoadFailureDetails = std::move(Details);
}
void lcTexture::SetImage(std::vector<Image>&& Images, int Flags)
void lcTexture::SetImage(std::vector<QImage>&& Images, int Flags)
{
mImages = std::move(Images);
mFlags = Flags;
@@ -180,8 +206,8 @@ void lcTexture::Upload(lcContext* Context)
if (!NeedsUpload())
return;
mWidth = mImages[0].mWidth;
mHeight = mImages[0].mHeight;
mWidth = mImages[0].width();
mHeight = mImages[0].height();
const bool Uploaded = Context->UploadTexture(this);
@@ -207,8 +233,16 @@ void lcTexture::Upload(lcContext* Context)
bool lcTexture::LoadImages()
{
for (Image& Image : mImages)
Image.ResizePow2();
for (QImage& Image : mImages)
{
Image = PrepareImage(std::move(Image));
if (Image.isNull())
{
SetFailed(lcTextureLoadError::DecodeFailed, QString());
return false;
}
}
mState = lcTextureState::Decoded;
+6 -6
View File
@@ -15,7 +15,6 @@
#define LC_TEXTURE_NAME_LEN 256
#include "image.h"
#include "lc_filesource.h"
enum class lcTextureState
@@ -50,10 +49,11 @@ public:
lcTexture& operator=(lcTexture&&) = delete;
void CreateGridTexture();
static QImage PrepareImage(QImage Image);
void SetImage(Image&& Image, int Flags = 0);
void SetImage(std::vector<Image>&& Images, int Flags = 0);
void AdoptDecodedImage(Image&& Image, int Flags = 0);
void SetImage(QImage Image, int Flags);
void SetImage(std::vector<QImage>&& Images, int Flags);
void AdoptDecodedImage(QImage Image, int Flags);
void DiscardDecodedImage();
// Records a failure without discarding pixels retained for upload retries.
void SetFailed(lcTextureLoadError Error, QString Details);
@@ -112,7 +112,7 @@ public:
return mFlags;
}
const Image& GetImage(int Index) const
const QImage& GetImage(int Index) const
{
return mImages[Index];
}
@@ -137,7 +137,7 @@ protected:
bool mTemporary = false;
QAtomicInt mRefCount = 0;
std::vector<Image> mImages;
std::vector<QImage> mImages;
int mFlags = 0;
};
+33 -5
View File
@@ -64,8 +64,13 @@ lcTextureBuildResult lcTextureManager::BuildTextureData(lcFile& File)
{
lcTextureBuildResult Result = DecodeTextureData(File);
if (Result.DecodedImage)
Result.DecodedImage->ResizePow2();
if (!Result.DecodedImage.isNull())
{
Result.DecodedImage = lcTexture::PrepareImage(std::move(Result.DecodedImage));
if (Result.DecodedImage.isNull())
Result.Error = lcTextureLoadError::DecodeFailed;
}
return Result;
}
@@ -74,11 +79,34 @@ lcTextureBuildResult lcTextureManager::DecodeTextureData(lcFile& File)
{
lcTextureBuildResult Result;
Result.DecodedImage.reset(new Image);
bool Loaded = false;
if (!Result.DecodedImage->FileLoad(File))
if (lcMemFile* MemoryFile = dynamic_cast<lcMemFile*>(&File))
{
Result.DecodedImage.reset();
if (MemoryFile->mBuffer && MemoryFile->mPosition < MemoryFile->mFileSize)
{
const size_t Length = MemoryFile->mFileSize - MemoryFile->mPosition;
if (Length <= static_cast<size_t>(std::numeric_limits<int>::max()))
Loaded = Result.DecodedImage.loadFromData(MemoryFile->mBuffer + MemoryFile->mPosition, static_cast<int>(Length));
}
}
else if (lcDiskFile* DiskFile = dynamic_cast<lcDiskFile*>(&File))
{
QFile& Device = DiskFile->GetFile();
const qint64 Position = Device.pos();
if (Position >= 0)
{
Loaded = Result.DecodedImage.load(&Device, nullptr);
const bool Restored = Device.seek(Position);
Loaded = Loaded && Restored;
}
}
if (!Loaded || Result.DecodedImage.isNull())
{
Result.DecodedImage = QImage();
Result.Error = lcTextureLoadError::DecodeFailed;
}
+2 -1
View File
@@ -14,7 +14,7 @@ struct lcTextureBuildResult
lcTextureBuildResult(const lcTextureBuildResult&) = delete;
lcTextureBuildResult& operator=(const lcTextureBuildResult&) = delete;
std::unique_ptr<Image> DecodedImage;
QImage DecodedImage;
lcTextureLoadError Error{};
QString ErrorDetails;
};
@@ -42,6 +42,7 @@ public:
void ReleaseTexture(lcTexture* Texture);
// Worker-safe private CPU build; never publishes or uploads the result.
// Decodes from the current file position without advancing its cursor.
lcTextureBuildResult BuildTextureData(lcFile& File);
private:
+9 -12
View File
@@ -5,7 +5,6 @@
#include "lc_context.h"
#include "lc_stringcache.h"
#include "lc_application.h"
#include "image.h"
#include "lc_texture.h"
lcTexture* lcViewSphere::mTexture;
@@ -76,15 +75,14 @@ void lcViewSphere::CreateResources(lcContext* Context)
QImage PainterImage(SourceCellSize, SourceCellSize, QImage::Format_ARGB32);
QPainter Painter;
QFont Font("Helvetica", 36);
Image AtlasImage;
AtlasImage.Allocate(AtlasWidth, AtlasHeight, lcPixelFormat::A8);
memset(AtlasImage.mData, 0, AtlasWidth * AtlasHeight);
QImage AtlasImage(AtlasWidth, AtlasHeight, QImage::Format_Alpha8);
AtlasImage.fill(0);
auto ConvertToSDF = [](Image& TextureImage)
auto ConvertToSDF = [](QImage& TextureImage)
{
constexpr int Size = 128, Count = Size * Size;
std::vector<int> ToInk(Count), ToOutside(Count);
for (int i = 0; i < Count; i++) { ToInk[i] = TextureImage.mData[i] >= 128 ? 0 : 1 << 20; ToOutside[i] = TextureImage.mData[i] >= 128 ? 1 << 20 : 0; }
for (int i = 0; i < Count; i++) { ToInk[i] = TextureImage.constScanLine(i / Size)[i % Size] >= 128 ? 0 : 1 << 20; ToOutside[i] = TextureImage.constScanLine(i / Size)[i % Size] >= 128 ? 1 << 20 : 0; }
auto Relax = [](std::vector<int>& Distance, bool Reverse)
{
for (int y = Reverse ? Size - 1 : 0; Reverse ? y >= 0 : y < Size; y += Reverse ? -1 : 1)
@@ -97,13 +95,12 @@ void lcViewSphere::CreateResources(lcContext* Context)
}
};
Relax(ToInk, false); Relax(ToInk, true); Relax(ToOutside, false); Relax(ToOutside, true);
for (int i = 0; i < Count; i++) TextureImage.mData[i] = static_cast<unsigned char>(lcClamp(128 + (ToOutside[i] - ToInk[i]) / 3 * 16, 0, 255));
for (int i = 0; i < Count; i++) TextureImage.scanLine(i / Size)[i % Size] = static_cast<unsigned char>(lcClamp(128 + (ToOutside[i] - ToInk[i]) / 3 * 16, 0, 255));
};
for (int ViewIdx = 0; ViewIdx < 6; ViewIdx++)
{
Image TextureImage;
TextureImage.Allocate(SourceCellSize, SourceCellSize, lcPixelFormat::A8);
QImage TextureImage(SourceCellSize, SourceCellSize, QImage::Format_Alpha8);
QFont LabelFont = Font;
// The shader displays the middle 80% of each cell. Leave six pixels
// on either side for the SDF halo, including for translated labels.
@@ -122,7 +119,7 @@ void lcViewSphere::CreateResources(lcContext* Context)
for (int y = 0; y < SourceCellSize; y++)
{
unsigned char* Dest = TextureImage.mData + (SourceCellSize - y - 1) * TextureImage.mWidth;
unsigned char* Dest = TextureImage.scanLine(SourceCellSize - y - 1);
for (int x = 0; x < SourceCellSize; x++)
*Dest++ = qRed(PainterImage.pixel(x, y));
@@ -131,8 +128,8 @@ void lcViewSphere::CreateResources(lcContext* Context)
// Keep the middle band and its SDF margin in a vertical atlas.
const int AtlasY = ViewIdx * CellHeight;
for (int y = 0; y < CellHeight; y++)
memcpy(AtlasImage.mData + (AtlasY + y) * AtlasWidth,
TextureImage.mData + (CellBottom + y) * SourceCellSize, SourceCellSize);
memcpy(AtlasImage.scanLine(AtlasY + y),
TextureImage.constScanLine(CellBottom + y), SourceCellSize);
}
mTexture->SetImage(std::move(AtlasImage), LC_TEXTURE_LINEAR);
-1
View File
@@ -7,7 +7,6 @@
#include "camera.h"
#include "project.h"
#include "lc_instructions.h"
#include "image.h"
#include "light.h"
#include "lc_mainwindow.h"
#include "lc_view.h"
+4 -6
View File
@@ -3,7 +3,6 @@
#include "texfont.h"
#include "lc_context.h"
#include "lc_texture.h"
#include "image.h"
static const unsigned char GlyphData[] =
{
@@ -33,9 +32,8 @@ bool TexFont::Initialize(lcContext* Context)
constexpr int Scale = 4, Padding = 8, TileWidth = 96, TileHeight = 80;
mTextureWidth = mTextureHeight = 1024;
Image Image;
Image.Allocate(mTextureWidth, mTextureHeight, lcPixelFormat::L8A8);
memset(Image.mData, 0, static_cast<size_t>(mTextureWidth) * mTextureHeight * 2);
QImage Image(mTextureWidth, mTextureHeight, QImage::Format_Alpha8);
Image.fill(0);
QFont Font(QStringLiteral("Arial"));
Font.setPixelSize(56);
@@ -94,8 +92,8 @@ bool TexFont::Initialize(lcContext* Context)
{
const int SignedDistance = (ToOutside[y * MaskWidth + x] - ToInk[y * MaskWidth + x]) / 3;
const unsigned char Value = static_cast<unsigned char>(lcClamp(128 + SignedDistance * 8, 0, 255));
unsigned char* Pixel = Image.mData + ((TileY + y) * mTextureWidth + TileX + x) * 2;
Pixel[0] = Pixel[1] = Value;
unsigned char* Pixel = Image.scanLine(TileY + y) + TileX + x;
*Pixel = Value;
}
mGlyphs[Glyph].Width = Advance / static_cast<float>(Scale);
+10 -1
View File
@@ -122,7 +122,7 @@ Library unload drains loader work, destroys meshes that release texture referenc
clears the texture manager, then closes archives. Workers must be drained before
the manager is cleared or archive indexes are replaced.
`Image::FileLoad(lcFile&)` decodes disk files through their existing `QFile` and
`lcTextureManager::DecodeTextureData(lcFile&)` decodes disk files through their existing `QFile` and
memory files through `QImage::loadFromData()`, without an additional device adapter
or disk-to-memory copy. Disk cursor restoration runs on success and failure;
memory decoding leaves the cursor unchanged and validates the remaining buffer
@@ -131,6 +131,15 @@ private to each request, not shared between workers. Texture readiness waits use
the same loader worker path; unused direct texture-loading overloads and their
synchronous library/manager forwarding chain have been removed.
Decoded worker results hold a `QImage`, normalized to RGB888 or RGBA8888 and
scaled with Qt's smooth transformation to the existing power-of-two dimensions.
Textures retain the decoded `QImage` through upload without a second pixel buffer.
Uploads honor Qt's scanline alignment, packing unusual row strides only when
needed. CPU mipmap generation copies pixel rows without padding into its existing
working buffer; its filtering, OpenGL formats, and shaders remain unchanged.
Each transfer sets its required unpack alignment, and texture upload restores
the caller's unpack alignment on every exit. Pack alignment is left unchanged.
`lcTexture::SetFailed()` assigns readiness and failure diagnostics together,
clearing stale details when none are supplied. Decoding and upload failures use
the same setter. It retains decoded pixels for bounded upload
-2
View File
@@ -174,7 +174,6 @@ ios {
SOURCES += \
common/camera.cpp \
common/group.cpp \
common/image.cpp \
common/lc_modelhistory.cpp \
common/light.cpp \
common/minifig.cpp \
@@ -255,7 +254,6 @@ SOURCES += \
HEADERS += \
common/camera.h \
common/group.h \
common/image.h \
common/lc_modelhistory.h \
common/lc_result.h \
common/light.h \