2020-05-23 22:15:38 +00:00
|
|
|
/*
|
|
|
|
** 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
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
2020-05-25 21:59:07 +00:00
|
|
|
FMultiPatchTexture::FMultiPatchTexture(int w, int h, const TArray<TexPartBuild> &parts, bool complex, bool textual)
|
2020-05-23 22:15:38 +00:00
|
|
|
{
|
|
|
|
Width = w;
|
|
|
|
Height = h;
|
|
|
|
bComplex = complex;
|
2020-05-25 21:59:07 +00:00
|
|
|
bTextual = textual;
|
2020-05-23 22:15:38 +00:00
|
|
|
Parts = (TexPart*)ImageArena.Alloc(sizeof(TexPart) * parts.Size());
|
|
|
|
NumParts = parts.Size();
|
|
|
|
memcpy(Parts, parts.Data(), sizeof(TexPart) * parts.Size());
|
2020-05-25 21:59:07 +00:00
|
|
|
for (unsigned i = 0; i < parts.Size(); i++)
|
|
|
|
{
|
|
|
|
Parts[i].Image = parts[i].TexImage->GetImage();
|
|
|
|
}
|
2020-05-23 22:15:38 +00:00
|
|
|
|
|
|
|
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.
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-25 21:59:07 +00:00
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
|
2020-05-23 22:15:38 +00:00
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// 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
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
2023-01-07 18:30:49 +00:00
|
|
|
PalettedPixels FMultiPatchTexture::CreatePalettedPixels(int conversion)
|
2020-05-23 22:15:38 +00:00
|
|
|
{
|
|
|
|
int numpix = Width * Height;
|
|
|
|
uint8_t blendwork[256];
|
|
|
|
bool buildrgb = bComplex;
|
|
|
|
|
2023-01-07 18:30:49 +00:00
|
|
|
PalettedPixels Pixels(numpix);
|
2020-05-23 22:15:38 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|