raze/source/common/textures/formats/multipatchtexture.cpp
2023-01-07 19:30:49 +01:00

359 lines
10 KiB
C++

/*
** multipatchtexture.cpp
** Texture class for standard Doom multipatch textures
**
**---------------------------------------------------------------------------
** Copyright 2004-2006 Randy Heit
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
** derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**---------------------------------------------------------------------------
**
**
*/
#include <ctype.h>
#include "files.h"
#include "filesystem.h"
#include "image.h"
#include "multipatchtexture.h"
#include "imagehelpers.h"
//==========================================================================
//
// FMultiPatchTexture :: FMultiPatchTexture
//
//==========================================================================
FMultiPatchTexture::FMultiPatchTexture(int w, int h, const TArray<TexPartBuild> &parts, bool complex, bool textual)
{
Width = w;
Height = h;
bComplex = complex;
bTextual = textual;
Parts = (TexPart*)ImageArena.Alloc(sizeof(TexPart) * parts.Size());
NumParts = parts.Size();
memcpy(Parts, parts.Data(), sizeof(TexPart) * parts.Size());
for (unsigned i = 0; i < parts.Size(); i++)
{
Parts[i].Image = parts[i].TexImage->GetImage();
}
bUseGamePalette = false;
if (!bComplex)
{
for (int i = 0; i < NumParts; i++)
{
if (!Parts[i].Image->UseGamePalette()) return;
}
bUseGamePalette = true; // only if all patches use the game palette.
}
}
//==========================================================================
//
// sky remapping will only happen if
// - the texture was defined through a TEXTUREx lump (this implies only trivial copies)
// - all patches use the base palette.
// - all patches are in a format that allows the remap.
// All other cases would not be able to properly deal with this special case.
// For textual definitions this hack isn't necessary.
//
//==========================================================================
bool FMultiPatchTexture::SupportRemap0()
{
if (bTextual || UseGamePalette()) return false;
for (int i = 0; i < NumParts; i++)
{
if (!Parts[i].Image->SupportRemap0()) return false;
}
return true;
}
//==========================================================================
//
// GetBlendMap
//
//==========================================================================
static uint8_t *GetBlendMap(PalEntry blend, uint8_t *blendwork)
{
switch (blend.a==0 ? int(blend) : -1)
{
case BLEND_ICEMAP:
return GPalette.IceMap.Remap;
default:
if (blend >= BLEND_SPECIALCOLORMAP1 && blend < BLEND_SPECIALCOLORMAP1 + SpecialColormaps.Size())
{
return SpecialColormaps[blend - BLEND_SPECIALCOLORMAP1].Colormap;
}
else if (blend >= BLEND_DESATURATE1 && blend <= BLEND_DESATURATE31)
{
return DesaturateColormap[blend - BLEND_DESATURATE1];
}
else
{
blendwork[0]=0;
if (blend.a == 255)
{
for(int i=1;i<256;i++)
{
int rr = (blend.r * GPalette.BaseColors[i].r) / 255;
int gg = (blend.g * GPalette.BaseColors[i].g) / 255;
int bb = (blend.b * GPalette.BaseColors[i].b) / 255;
blendwork[i] = ColorMatcher.Pick(rr, gg, bb);
}
return blendwork;
}
else if (blend.a != 0)
{
for(int i=1;i<256;i++)
{
int rr = (blend.r * blend.a + GPalette.BaseColors[i].r * (255-blend.a)) / 255;
int gg = (blend.g * blend.a + GPalette.BaseColors[i].g * (255-blend.a)) / 255;
int bb = (blend.b * blend.a + GPalette.BaseColors[i].b * (255-blend.a)) / 255;
blendwork[i] = ColorMatcher.Pick(rr, gg, bb);
}
return blendwork;
}
}
}
return nullptr;
}
//==========================================================================
//
//
//
//==========================================================================
void FMultiPatchTexture::CopyToBlock(uint8_t *dest, int dwidth, int dheight, FImageSource *source, int xpos, int ypos, int rotate, const uint8_t *translation, int style)
{
auto cimage = source->GetCachedPalettedPixels(style); // should use composition cache
auto &image = cimage.Pixels;
const uint8_t *pixels = image.Data();
int srcwidth = source->GetWidth();
int srcheight = source->GetHeight();
int step_x = source->GetHeight();
int step_y = 1;
FClipRect cr = { 0, 0, dwidth, dheight };
if (style) translation = nullptr; // do not apply translations to alpha textures.
if (ClipCopyPixelRect(&cr, xpos, ypos, pixels, srcwidth, srcheight, step_x, step_y, rotate))
{
dest += ypos + dheight * xpos;
if (translation == NULL)
{
for (int x = 0; x < srcwidth; x++)
{
int pos = x * dheight;
for (int y = 0; y < srcheight; y++, pos++)
{
// the optimizer is doing a good enough job here so there's no need to optimize this by hand
uint8_t v = pixels[y * step_y + x * step_x];
if (v != 0) dest[pos] = v;
}
}
}
else
{
for (int x = 0; x < srcwidth; x++)
{
int pos = x * dheight;
for (int y = 0; y < srcheight; y++, pos++)
{
uint8_t v = pixels[y * step_y + x * step_x];
if (v != 0) dest[pos] = translation[v];
}
}
}
}
}
//==========================================================================
//
// FMultiPatchTexture :: MakeTexture
//
//==========================================================================
PalettedPixels FMultiPatchTexture::CreatePalettedPixels(int conversion)
{
int numpix = Width * Height;
uint8_t blendwork[256];
bool buildrgb = bComplex;
PalettedPixels Pixels(numpix);
memset (Pixels.Data(), 0, numpix);
if (conversion == luminance)
{
// For alpha textures, downconversion to the palette would lose too much precision if not all patches use the palette.
buildrgb = !UseGamePalette();
}
else
{
// For regular textures we can use paletted compositing if all patches are just being copied because they all can create a paletted buffer.
if (!buildrgb) for (int i = 0; i < NumParts; ++i)
{
if (Parts[i].op != OP_COPY)
{
buildrgb = true;
}
}
}
if (conversion == noremap0)
{
if (bTextual || !UseGamePalette()) conversion = normal;
}
if (!buildrgb)
{
for (int i = 0; i < NumParts; ++i)
{
uint8_t *trans = Parts[i].Translation? Parts[i].Translation->Remap : nullptr;
{
if (Parts[i].Blend != 0)
{
trans = GetBlendMap(Parts[i].Blend, blendwork);
}
CopyToBlock (Pixels.Data(), Width, Height, Parts[i].Image, Parts[i].OriginX, Parts[i].OriginY, Parts[i].Rotate, trans, conversion);
}
}
}
else
{
// In case there are translucent patches let's do the composition in
// True color to keep as much precision as possible before downconverting to the palette.
FBitmap PixelsIn;
PixelsIn.Create(Width, Height);
CopyPixels(&PixelsIn, normal);
for(int y = 0; y < Height; y++)
{
uint8_t *in = PixelsIn.GetPixels() + Width * y * 4;
uint8_t *out = Pixels.Data() + y;
for (int x = 0; x < Width; x++)
{
if (*out == 0 && in[3] != 0)
{
*out = ImageHelpers::RGBToPalette(conversion == luminance, in[2], in[1], in[0]);
}
out += Height;
in += 4;
}
}
}
return Pixels;
}
//===========================================================================
//
// FMultipatchTexture::CopyTrueColorPixels
//
// Preserves the palettes of each individual patch
//
//===========================================================================
int FMultiPatchTexture::CopyPixels(FBitmap *bmp, int conversion)
{
int retv = -1;
if (conversion == noremap0)
{
if (bTextual || !UseGamePalette()) conversion = normal;
}
for(int i = 0; i < NumParts; i++)
{
int ret = -1;
FCopyInfo info;
memset (&info, 0, sizeof(info));
info.alpha = Parts[i].Alpha;
info.invalpha = BLENDUNIT - info.alpha;
info.op = ECopyOp(Parts[i].op);
PalEntry b = Parts[i].Blend;
if (b.a == 0 && b != BLEND_NONE)
{
info.blend = EBlend(b.d);
}
else if (b.a != 0)
{
if (b.a == 255)
{
info.blendcolor[0] = b.r * BLENDUNIT / 255;
info.blendcolor[1] = b.g * BLENDUNIT / 255;
info.blendcolor[2] = b.b * BLENDUNIT / 255;
info.blend = BLEND_MODULATE;
}
else
{
blend_t blendalpha = b.a * BLENDUNIT / 255;
info.blendcolor[0] = b.r * blendalpha;
info.blendcolor[1] = b.g * blendalpha;
info.blendcolor[2] = b.b * blendalpha;
info.blendcolor[3] = FRACUNIT - blendalpha;
info.blend = BLEND_OVERLAY;
}
}
auto trans = Parts[i].Translation ? Parts[i].Translation->Palette : nullptr;
FBitmap Pixels = Parts[i].Image->GetCachedBitmap(trans, conversion, &ret);
bmp->Blit(Parts[i].OriginX, Parts[i].OriginY, Pixels, Pixels.GetWidth(), Pixels.GetHeight(), Parts[i].Rotate, &info);
// treat -1 (i.e. unknown) as absolute. We have no idea if this may have overwritten previous info so a real check needs to be done.
if (ret == -1) retv = ret;
else if (retv != -1 && ret > retv) retv = ret;
}
return retv;
}
//==========================================================================
//
//
//
//==========================================================================
void FMultiPatchTexture::CollectForPrecache(PrecacheInfo &info, bool requiretruecolor)
{
FImageSource::CollectForPrecache(info, requiretruecolor);
if (!requiretruecolor)
{
requiretruecolor = bComplex;
if (!requiretruecolor) for (int i = 0; i < NumParts; ++i)
{
if (Parts[i].op != OP_COPY) requiretruecolor = true;
}
}
for (int i = 0; i < NumParts; ++i)
{
Parts[i].Image->CollectForPrecache(info, requiretruecolor);
}
}