/* ** 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 #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 &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 // //========================================================================== TArray FMultiPatchTexture::CreatePalettedPixels(int conversion) { int numpix = Width * Height; uint8_t blendwork[256]; bool buildrgb = bComplex; TArray Pixels(numpix, true); 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); } }