Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Single layer image processing improvements #8276

Merged
merged 16 commits into from
Jan 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
bfe72a2
Don't interact with the transform layer if the image is a single layer.
Districh-ru Dec 17, 2023
dc56af5
Add assertion to get transform layer only for double-layer images, fi…
Districh-ru Dec 24, 2023
c7d6c6e
Fix fheroes2::Resize() for the case it in and out images have differe…
Districh-ru Dec 24, 2023
12fb09f
Merge branch 'ihhub:master' into single-layer-do-not-access-transform
Districh-ru Dec 24, 2023
ca8874d
Add assertions to more places where transform layer is accessed
Districh-ru Dec 24, 2023
309266e
Do not allocate memory for transform() layer when image is single-layer
Districh-ru Dec 24, 2023
03c726e
Merge branch 'master' into single-layer-no-transform-memory-allocate
Districh-ru Dec 28, 2023
c05b8c6
Merge branch 'ihhub:master' into single-layer-do-not-access-transform
Districh-ru Jan 4, 2024
8782450
Apply IWYU suggestion
Districh-ru Jan 4, 2024
8377456
Revert "Apply IWYU suggestion"
Districh-ru Jan 4, 2024
67edb14
Update copyrights ... can it be automated? :)
Districh-ru Jan 4, 2024
87733e7
Merge branch 'single-layer-do-not-access-transform' into single-layer…
Districh-ru Jan 4, 2024
6a83307
Merge branch 'master' into single-layer-no-transform-memory-allocate
Districh-ru Jan 7, 2024
dbb5fb8
Update copyright years where needed
Districh-ru Jan 7, 2024
6e7fe0c
Merge branch 'ihhub:master' into single-layer-no-transform-memory-all…
Districh-ru Jan 14, 2024
02c9de4
Revert changes done to transform layer resetting and memory allocation
Districh-ru Jan 14, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions src/engine/h2d_file.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/***************************************************************************
* fheroes2: https://github.com/ihhub/fheroes2 *
* Copyright (C) 2021 - 2023 *
* Copyright (C) 2021 - 2024 *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
Expand Down Expand Up @@ -173,6 +173,9 @@ namespace fheroes2

bool readImageFromH2D( H2DReader & reader, const std::string & name, Sprite & image )
{
// TODO: Store in h2d images the 'isSingleLayer' state to disable and skip transform layer for such images.
assert( !image.singleLayer() );

const std::vector<uint8_t> & data = reader.getFile( name );
if ( data.size() < 4 + 4 + 4 + 4 + 1 ) {
// Empty or invalid image.
Expand All @@ -191,7 +194,6 @@ namespace fheroes2
const size_t size = static_cast<size_t>( width * height );
image.resize( width, height );
memcpy( image.image(), data.data() + 4 + 4 + 4 + 4, size );
// TODO: Store in h2d images the 'isSingleLayer' state to disable and skip transform layer for such images.
memcpy( image.transform(), data.data() + 4 + 4 + 4 + 4 + size, size );

image.setPosition( x, y );
Expand All @@ -201,7 +203,8 @@ namespace fheroes2

bool writeImageToH2D( H2DWriter & writer, const std::string & name, const Sprite & image )
{
assert( !image.empty() );
// TODO: Store in h2d images the 'isSingleLayer' state to disable and skip transform layer for such images.
assert( !image.empty() && !image.singleLayer() );

StreamBuf stream;
stream.putLE32( static_cast<uint32_t>( image.width() ) );
Expand Down
124 changes: 92 additions & 32 deletions src/engine/image.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -519,9 +519,9 @@ namespace fheroes2
return;
}

const size_t size = static_cast<size_t>( width_ ) * height_;
ihhub marked this conversation as resolved.
Show resolved Hide resolved
const size_t size = static_cast<size_t>( width_ ) * height_ * 2;

_data.reset( new uint8_t[size * 2] );
_data.reset( new uint8_t[size] );

_width = width_;
_height = height_;
Expand All @@ -545,18 +545,18 @@ namespace fheroes2
return;
}

const size_t size = static_cast<size_t>( image._width ) * image._height;
const size_t size = static_cast<size_t>( image._width ) * image._height * 2;

_singleLayer = image._singleLayer;

if ( image._width != _width || image._height != _height ) {
_data.reset( new uint8_t[size * 2] );
_data.reset( new uint8_t[size] );

_width = image._width;
_height = image._height;
}

_singleLayer = image._singleLayer;

memcpy( _data.get(), image._data.get(), size * 2 );
memcpy( _data.get(), image._data.get(), size );
}

Sprite::Sprite( const int32_t width_, const int32_t height_, const int32_t x_ /* = 0 */, const int32_t y_ /* = 0 */ )
Expand Down Expand Up @@ -2722,7 +2722,18 @@ namespace fheroes2

const uint8_t * gamePalette = getGamePalette();

if ( in.singleLayer() && out.singleLayer() ) {
if ( in.singleLayer() ) {
if ( !out.singleLayer() ) {
// In this case we make the output image fully non-transparent in the given output area.

uint8_t * transformY = out.transform() + outY * widthOut + outX;
Districh-ru marked this conversation as resolved.
Show resolved Hide resolved
const uint8_t * transformYEnd = transformY + heightRoiOut * widthOut;
Districh-ru marked this conversation as resolved.
Show resolved Hide resolved

for ( ; transformY != transformYEnd; transformY += widthOut ) {
memset( transformY, static_cast<uint8_t>( 0 ), widthRoiOut );
}
}

for ( int32_t y = 0; y < heightRoiOut; ++y, imageOutY += widthOut ) {
const double posY = static_cast<double>( y * heightRoiIn ) / heightRoiOut;
const int32_t startY = static_cast<int32_t>( posY );
Expand Down Expand Up @@ -2763,52 +2774,63 @@ namespace fheroes2
}
else {
const uint8_t * transformInY = in.transform() + offsetInY;
uint8_t * transformOutY = out.transform() + offsetOutY;
const bool isOutNotSingleLayer = !out.singleLayer();
uint8_t * transformOutY = isOutNotSingleLayer ? ( out.transform() + offsetOutY ) : nullptr;

for ( int32_t y = 0; y < heightRoiOut; ++y, imageOutY += widthOut, transformOutY += widthOut ) {
for ( int32_t y = 0; y < heightRoiOut; ++y, imageOutY += widthOut ) {
const double posY = static_cast<double>( y * heightRoiIn ) / heightRoiOut;
const int32_t startY = static_cast<int32_t>( posY );
const double coeffY = posY - startY;

uint8_t * imageOutX = imageOutY;
uint8_t * transformOutX = transformOutY;

for ( int32_t x = 0; x < widthRoiOut; ++x, ++imageOutX, ++transformOutX ) {
for ( int32_t x = 0; x < widthRoiOut; ++x, ++imageOutX ) {
const double posX = positionX[x];
const int32_t startX = static_cast<int32_t>( posX );
const int32_t offsetIn = startY * widthIn + startX;

const uint8_t * imageInX = imageInY + offsetIn;
const uint8_t * transformInX = transformInY + offsetIn;

if ( posX < widthIn - 1 && posY < heightRoiIn - 1 ) {
if ( *transformInX == 0 && *( transformInX + 1 ) == 0 && *( transformInX + widthRoiIn ) == 0 && *( transformInX + widthRoiIn + 1 ) == 0 ) {
const double coeffX = posX - startX;
const double coeff1 = ( 1 - coeffX ) * ( 1 - coeffY );
const double coeff2 = coeffX * ( 1 - coeffY );
const double coeff3 = ( 1 - coeffX ) * coeffY;
const double coeff4 = coeffX * coeffY;
if ( posX < widthIn - 1 && posY < heightRoiIn - 1 && *transformInX == 0 && *( transformInX + 1 ) == 0 && *( transformInX + widthRoiIn ) == 0
&& *( transformInX + widthRoiIn + 1 ) == 0 ) {
const double coeffX = posX - startX;
const double coeff1 = ( 1 - coeffX ) * ( 1 - coeffY );
const double coeff2 = coeffX * ( 1 - coeffY );
const double coeff3 = ( 1 - coeffX ) * coeffY;
const double coeff4 = coeffX * coeffY;

const uint8_t * id1 = gamePalette + static_cast<uint32_t>( *imageInX ) * 3;
const uint8_t * id2 = gamePalette + static_cast<uint32_t>( *( imageInX + 1 ) ) * 3;
const uint8_t * id3 = gamePalette + static_cast<uint32_t>( *( imageInX + widthIn ) ) * 3;
const uint8_t * id4 = gamePalette + static_cast<uint32_t>( *( imageInX + widthIn + 1 ) ) * 3;
const uint8_t * id1 = gamePalette + static_cast<uint32_t>( *imageInX ) * 3;
Districh-ru marked this conversation as resolved.
Show resolved Hide resolved
const uint8_t * id2 = gamePalette + static_cast<uint32_t>( *( imageInX + 1 ) ) * 3;
Districh-ru marked this conversation as resolved.
Show resolved Hide resolved
const uint8_t * id3 = gamePalette + static_cast<uint32_t>( *( imageInX + widthIn ) ) * 3;
Districh-ru marked this conversation as resolved.
Show resolved Hide resolved
const uint8_t * id4 = gamePalette + static_cast<uint32_t>( *( imageInX + widthIn + 1 ) ) * 3;
Districh-ru marked this conversation as resolved.
Show resolved Hide resolved

const double red = *id1 * coeff1 + *id2 * coeff2 + *id3 * coeff3 + *id4 * coeff4 + 0.5;
const double green = *( id1 + 1 ) * coeff1 + *( id2 + 1 ) * coeff2 + *( id3 + 1 ) * coeff3 + *( id4 + 1 ) * coeff4 + 0.5;
const double blue = *( id1 + 2 ) * coeff1 + *( id2 + 2 ) * coeff2 + *( id3 + 2 ) * coeff3 + *( id4 + 2 ) * coeff4 + 0.5;
const double red = *id1 * coeff1 + *id2 * coeff2 + *id3 * coeff3 + *id4 * coeff4 + 0.5;
const double green = *( id1 + 1 ) * coeff1 + *( id2 + 1 ) * coeff2 + *( id3 + 1 ) * coeff3 + *( id4 + 1 ) * coeff4 + 0.5;
const double blue = *( id1 + 2 ) * coeff1 + *( id2 + 2 ) * coeff2 + *( id3 + 2 ) * coeff3 + *( id4 + 2 ) * coeff4 + 0.5;

*imageOutX = GetPALColorId( static_cast<uint8_t>( red ), static_cast<uint8_t>( green ), static_cast<uint8_t>( blue ) );
}
else {
*imageOutX = GetPALColorId( static_cast<uint8_t>( red ), static_cast<uint8_t>( green ), static_cast<uint8_t>( blue ) );
}
else {
if ( isOutNotSingleLayer || *transformInX == 0 ) {
// Output image is double-layer or single-layer with non-transparent current pixel.
*imageOutX = *imageInX;
}
else if ( *transformInX != 1 ) {
// Apply a transformation.
*imageOutX = *( transformTable + ( *transformInX ) * 256 + *imageOutX );
Districh-ru marked this conversation as resolved.
Show resolved Hide resolved
}
}
else {
*imageOutX = *imageInX;

if ( isOutNotSingleLayer ) {
*transformOutX = *transformInX;
++transformOutX;
}
}

*transformOutX = *transformInX;
if ( !isOutNotSingleLayer ) {
transformOutY += widthOut;
}
}
}
Expand All @@ -2823,7 +2845,18 @@ namespace fheroes2
positionX[x] = ( x * widthRoiIn ) / widthRoiOut;
}

if ( in.singleLayer() && out.singleLayer() ) {
if ( in.singleLayer() ) {
if ( !out.singleLayer() ) {
// In this case we make the output image fully non-transparent in the given output area.

uint8_t * transformY = out.transform() + outY * widthOut + outX;
Districh-ru marked this conversation as resolved.
Show resolved Hide resolved
const uint8_t * transformYEnd = transformY + heightRoiOut * widthOut;
Districh-ru marked this conversation as resolved.
Show resolved Hide resolved

for ( ; transformY != transformYEnd; transformY += widthOut ) {
memset( transformY, static_cast<uint8_t>( 0 ), widthRoiOut );
}
}

for ( ; imageOutY != imageOutYEnd; imageOutY += widthOut, ++idY ) {
uint8_t * imageOutX = imageOutY;

Expand All @@ -2836,7 +2869,34 @@ namespace fheroes2
}
}
}
else if ( out.singleLayer() ) {
const uint8_t * transformInY = in.transform() + offsetInY;

for ( ; imageOutY != imageOutYEnd; imageOutY += widthOut, ++idY ) {
uint8_t * imageOutX = imageOutY;

const int32_t offset = ( ( idY * heightRoiIn ) / heightRoiOut ) * widthIn;
const uint8_t * imageInX = imageInY + offset;
const uint8_t * transformInX = transformInY + offset;

for ( const int32_t posX : positionX ) {
const uint8_t * transformIn = transformInX + posX;
if ( *transformIn > 0 ) {
if ( *transformIn != 1 ) {
// Apply a transformation.
*imageOutX = *( transformTable + ( *transformIn ) * 256 + *imageOutX );
Districh-ru marked this conversation as resolved.
Show resolved Hide resolved
}
}
else {
*imageOutX = *( imageInX + posX );
}

++imageOutX;
}
}
}
else {
// Both 'in' and 'out' are double-layer.
const uint8_t * transformInY = in.transform() + offsetInY;
uint8_t * transformOutY = out.transform() + offsetOutY;

Expand Down
7 changes: 4 additions & 3 deletions src/engine/image.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/***************************************************************************
* fheroes2: https://github.com/ihhub/fheroes2 *
* Copyright (C) 2020 - 2023 *
* Copyright (C) 2020 - 2024 *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
Expand Down Expand Up @@ -28,7 +28,7 @@

namespace fheroes2
{
// Image contains image layer and transform layer.
// Image always contains an image layer and if image is not a single-layer then also a transform layer.
// - image layer contains visible pixels which are copy to a destination image
// - transform layer is used to apply some transformation to an image on which we draw the current one. For example, shadowing
class Image
Expand Down Expand Up @@ -77,13 +77,14 @@ namespace fheroes2
}

void reset(); // makes image fully transparent (transform layer is set to 1)

void clear(); // makes the image empty

// Fill 'image' layer with given value, setting 'transform' layer to 0.
void fill( const uint8_t value );

// This is an optional indicator for image processing functions.
// The whole image still consists of 2 layers but transform layer might be ignored in computations.
// The whole image still consists of 2 layers but transform layer might be ignored in computations
bool singleLayer() const
{
return _singleLayer;
Expand Down
8 changes: 7 additions & 1 deletion src/engine/image_tool.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/***************************************************************************
* fheroes2: https://github.com/ihhub/fheroes2 *
* Copyright (C) 2020 - 2023 *
* Copyright (C) 2020 - 2024 *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
Expand Down Expand Up @@ -155,6 +155,12 @@ namespace fheroes2

bool Load( const std::string & path, Image & image )
{
if ( image.singleLayer() ) {
// Output image should be double-layer!
assert( 0 );
return false;
}

std::unique_ptr<SDL_Surface, std::function<void( SDL_Surface * )>> surface( nullptr, SDL_FreeSurface );
std::unique_ptr<SDL_Surface, std::function<void( SDL_Surface * )>> loadedSurface( nullptr, SDL_FreeSurface );

Expand Down
10 changes: 5 additions & 5 deletions src/engine/localevent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1238,14 +1238,14 @@ void LocalEvent::HandleRenderDeviceResetEvent()
{
// All textures has to be recreated. The only way to do it is to reset everything and render it back.
fheroes2::Display & display = fheroes2::Display::instance();
fheroes2::Image temp( display.width(), display.height() );
if ( display.singleLayer() ) {
temp._disableTransformLayer();
}
fheroes2::Image temp;

assert( display.singleLayer() );

temp._disableTransformLayer();

fheroes2::Copy( display, temp );
display.release();
display.resize( temp.width(), temp.height() );
fheroes2::Copy( temp, display );
}

Expand Down
15 changes: 10 additions & 5 deletions src/engine/screen.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/***************************************************************************
* fheroes2: https://github.com/ihhub/fheroes2 *
* Copyright (C) 2020 - 2023 *
* Copyright (C) 2020 - 2024 *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
Expand Down Expand Up @@ -359,6 +359,12 @@ namespace

SDL_Surface * generateIconSurface( const fheroes2::Image & icon )
{
if ( icon.empty() || icon.singleLayer() ) {
// What are you trying to do? Icon should have not empty both image and transform layers.
assert( 0 );
return nullptr;
}

SDL_Surface * surface = SDL_CreateRGBSurface( 0, icon.width(), icon.height(), 32, 0xFF, 0xFF00, 0xFF0000, 0xFF000000 );
if ( surface == nullptr ) {
ERROR_LOG( "Failed to create a surface of " << icon.width() << " x " << icon.height() << " size for cursor. The error: " << SDL_GetError() )
Expand Down Expand Up @@ -432,7 +438,7 @@ namespace

void update( const fheroes2::Image & image, int32_t offsetX, int32_t offsetY ) override
{
if ( image.empty() ) {
if ( image.empty() || image.singleLayer() ) {
// What are you trying to do? Set an invisible cursor? Use hide() method!
assert( 0 );
return;
Expand Down Expand Up @@ -1343,10 +1349,9 @@ namespace fheroes2
}

Image::resize( info.gameWidth, info.gameHeight );
_screenSize = { info.screenWidth, info.screenHeight };
Image::reset();

// To detect some UI artifacts by invalid code let's put all transform data into pixel skipping mode.
std::fill( transform(), transform() + width() * height(), static_cast<uint8_t>( 1 ) );
_screenSize = { info.screenWidth, info.screenHeight };
ihhub marked this conversation as resolved.
Show resolved Hide resolved
}

Display & Display::instance()
Expand Down
23 changes: 13 additions & 10 deletions src/engine/zzlib.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/***************************************************************************
* fheroes2: https://github.com/ihhub/fheroes2 *
* Copyright (C) 2019 - 2023 *
* Copyright (C) 2019 - 2024 *
* *
* Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 *
* Copyright (C) 2009 by Andrey Afletdinov <[email protected]> *
Expand Down Expand Up @@ -195,27 +195,30 @@ bool ZStreamBuf::write( const std::string & fn, const bool append /* = false */

fheroes2::Image CreateImageFromZlib( int32_t width, int32_t height, const uint8_t * imageData, size_t imageSize, bool doubleLayer )
{
if ( imageData == nullptr || imageSize == 0 || width <= 0 || height <= 0 )
return fheroes2::Image();
if ( imageData == nullptr || imageSize == 0 || width <= 0 || height <= 0 ) {
return {};
}

const std::vector<uint8_t> & uncompressedData = zlibDecompress( imageData, imageSize );
if ( doubleLayer && ( uncompressedData.size() & 1 ) == 1 ) {
return fheroes2::Image();
return {};
}

const size_t uncompressedSize = doubleLayer ? uncompressedData.size() / 2 : uncompressedData.size();

if ( static_cast<size_t>( width * height ) != uncompressedSize )
return fheroes2::Image();
if ( static_cast<size_t>( width * height ) != uncompressedSize ) {
Districh-ru marked this conversation as resolved.
Show resolved Hide resolved
return {};
}

fheroes2::Image out( width, height );
fheroes2::Image out;
if ( !doubleLayer ) {
out._disableTransformLayer();
}
out.resize( width, height );

std::memcpy( out.image(), uncompressedData.data(), uncompressedSize );
if ( doubleLayer ) {
std::memcpy( out.transform(), uncompressedData.data() + uncompressedSize, uncompressedSize );
}
else {
std::fill( out.transform(), out.transform() + uncompressedSize, static_cast<uint8_t>( 0 ) );
}
return out;
}
Loading