mirror of
https://github.com/leozide/leocad.git
synced 2026-10-27 06:20:55 +00:00
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:
1 parent
05ba97dd88
commit
b69db29428
19 files changed
+204
-353
No files matched your search
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -10,7 +10,6 @@ class lcMesh;
|
||||
class lcSynthInfo;
|
||||
class lcMemFile;
|
||||
class lcLibraryMeshData;
|
||||
class Image;
|
||||
class lcTexture;
|
||||
enum class lcTextureLoadError;
|
||||
|
||||
|
||||
+54
-25
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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,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"
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#define LC_IMAGE_TRANSPARENT 0x2000
|
||||
|
||||
enum class lcShadingMode;
|
||||
enum class lcSelectionRenderMode;
|
||||
enum class lcAxisIconLocation;
|
||||
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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 \
|
||||
|
||||
Reference in new issue
Block a user