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

486 lines
14 KiB
C++

/*
** jpegtexture.cpp
** Texture class for JPEG images
**
**---------------------------------------------------------------------------
** Copyright 2005-2016 Randy Heit
** Copyright 2005-2019 Christoph Oelckers
** 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 <stdio.h>
extern "C"
{
#include <jpeglib.h>
}
#include "files.h"
#include "filesystem.h"
#include "printf.h"
#include "bitmap.h"
#include "imagehelpers.h"
#include "image.h"
struct FLumpSourceMgr : public jpeg_source_mgr
{
FileReader *Lump;
JOCTET Buffer[4096];
bool StartOfFile;
FLumpSourceMgr (FileReader *lump, j_decompress_ptr cinfo);
static void InitSource (j_decompress_ptr cinfo);
static boolean FillInputBuffer (j_decompress_ptr cinfo);
static void SkipInputData (j_decompress_ptr cinfo, long num_bytes);
static void TermSource (j_decompress_ptr cinfo);
};
//==========================================================================
//
//
//
//==========================================================================
void FLumpSourceMgr::InitSource (j_decompress_ptr cinfo)
{
((FLumpSourceMgr *)(cinfo->src))->StartOfFile = true;
}
//==========================================================================
//
//
//
//==========================================================================
boolean FLumpSourceMgr::FillInputBuffer (j_decompress_ptr cinfo)
{
FLumpSourceMgr *me = (FLumpSourceMgr *)(cinfo->src);
auto nbytes = me->Lump->Read (me->Buffer, sizeof(me->Buffer));
if (nbytes <= 0)
{
me->Buffer[0] = (JOCTET)0xFF;
me->Buffer[1] = (JOCTET)JPEG_EOI;
nbytes = 2;
}
me->next_input_byte = me->Buffer;
me->bytes_in_buffer = nbytes;
me->StartOfFile = false;
return TRUE;
}
//==========================================================================
//
//
//
//==========================================================================
void FLumpSourceMgr::SkipInputData (j_decompress_ptr cinfo, long num_bytes)
{
FLumpSourceMgr *me = (FLumpSourceMgr *)(cinfo->src);
if (num_bytes <= (long)me->bytes_in_buffer)
{
me->bytes_in_buffer -= num_bytes;
me->next_input_byte += num_bytes;
}
else
{
num_bytes -= (long)me->bytes_in_buffer;
me->Lump->Seek (num_bytes, FileReader::SeekCur);
FillInputBuffer (cinfo);
}
}
//==========================================================================
//
//
//
//==========================================================================
void FLumpSourceMgr::TermSource (j_decompress_ptr cinfo)
{
}
//==========================================================================
//
//
//
//==========================================================================
FLumpSourceMgr::FLumpSourceMgr (FileReader *lump, j_decompress_ptr cinfo)
: Lump (lump)
{
cinfo->src = this;
init_source = InitSource;
fill_input_buffer = FillInputBuffer;
skip_input_data = SkipInputData;
resync_to_restart = jpeg_resync_to_restart;
term_source = TermSource;
bytes_in_buffer = 0;
next_input_byte = NULL;
}
//==========================================================================
//
//
//
//==========================================================================
void JPEG_ErrorExit (j_common_ptr cinfo)
{
(*cinfo->err->output_message) (cinfo);
throw -1;
}
//==========================================================================
//
//
//
//==========================================================================
void JPEG_OutputMessage (j_common_ptr cinfo)
{
char buffer[JMSG_LENGTH_MAX];
(*cinfo->err->format_message) (cinfo, buffer);
Printf (TEXTCOLOR_ORANGE "JPEG failure: %s\n", buffer);
}
//==========================================================================
//
// A JPEG texture
//
//==========================================================================
class FJPEGTexture : public FImageSource
{
public:
FJPEGTexture (int lumpnum, int width, int height);
int CopyPixels(FBitmap *bmp, int conversion) override;
PalettedPixels CreatePalettedPixels(int conversion) override;
};
//==========================================================================
//
//
//
//==========================================================================
FImageSource *JPEGImage_TryCreate(FileReader & data, int lumpnum)
{
union
{
uint32_t dw;
uint16_t w[2];
uint8_t b[4];
} first4bytes;
data.Seek(0, FileReader::SeekSet);
if (data.Read(&first4bytes, 4) < 4) return NULL;
if (first4bytes.b[0] != 0xFF || first4bytes.b[1] != 0xD8 || first4bytes.b[2] != 0xFF)
return NULL;
// Find the SOFn marker to extract the image dimensions,
// where n is 0, 1, or 2 (other types are unsupported).
while ((unsigned)first4bytes.b[3] - 0xC0 >= 3)
{
if (data.Read (first4bytes.w, 2) != 2)
{
return NULL;
}
data.Seek (BigShort(first4bytes.w[0]) - 2, FileReader::SeekCur);
if (data.Read (first4bytes.b + 2, 2) != 2 || first4bytes.b[2] != 0xFF)
{
return NULL;
}
}
if (data.Read (first4bytes.b, 3) != 3)
{
return NULL;
}
if (BigShort (first4bytes.w[0]) < 5)
{
return NULL;
}
if (data.Read (first4bytes.b, 4) != 4)
{
return NULL;
}
return new FJPEGTexture (lumpnum, BigShort(first4bytes.w[1]), BigShort(first4bytes.w[0]));
}
//==========================================================================
//
//
//
//==========================================================================
FJPEGTexture::FJPEGTexture (int lumpnum, int width, int height)
: FImageSource(lumpnum)
{
bMasked = false;
Width = width;
Height = height;
}
//==========================================================================
//
//
//
//==========================================================================
PalettedPixels FJPEGTexture::CreatePalettedPixels(int conversion)
{
auto lump = fileSystem.OpenFileReader (SourceLump);
JSAMPLE *buff = NULL;
jpeg_decompress_struct cinfo;
jpeg_error_mgr jerr;
PalettedPixels Pixels(Width * Height);
memset (Pixels.Data(), 0xBA, Width * Height);
cinfo.err = jpeg_std_error(&jerr);
cinfo.err->output_message = JPEG_OutputMessage;
cinfo.err->error_exit = JPEG_ErrorExit;
jpeg_create_decompress(&cinfo);
FLumpSourceMgr sourcemgr(&lump, &cinfo);
try
{
bool doalpha = conversion == luminance;
jpeg_read_header(&cinfo, TRUE);
if (!((cinfo.out_color_space == JCS_RGB && cinfo.num_components == 3) ||
(cinfo.out_color_space == JCS_CMYK && cinfo.num_components == 4) ||
(cinfo.out_color_space == JCS_YCCK && cinfo.num_components == 4) ||
(cinfo.out_color_space == JCS_YCbCr && cinfo.num_components == 3) ||
(cinfo.out_color_space == JCS_GRAYSCALE && cinfo.num_components == 1)))
{
Printf(TEXTCOLOR_ORANGE "Unsupported color format in %s\n", fileSystem.GetFileFullPath(SourceLump).GetChars());
}
else
{
jpeg_start_decompress(&cinfo);
int y = 0;
buff = new uint8_t[cinfo.output_width * cinfo.output_components];
while (cinfo.output_scanline < cinfo.output_height)
{
jpeg_read_scanlines(&cinfo, &buff, 1);
uint8_t *in = buff;
uint8_t *out = Pixels.Data() + y;
switch (cinfo.out_color_space)
{
case JCS_RGB:
for (int x = Width; x > 0; --x)
{
*out = ImageHelpers::RGBToPalette(doalpha, in[0], in[1], in[2]);
out += Height;
in += 3;
}
break;
case JCS_GRAYSCALE:
{
auto remap = ImageHelpers::GetRemap(doalpha, true);
for (int x = Width; x > 0; --x)
{
*out = remap[in[0]];
out += Height;
in += 1;
}
break;
}
case JCS_CMYK:
// What are you doing using a CMYK image? :)
for (int x = Width; x > 0; --x)
{
// To be precise, these calculations should use 255, but
// 256 is much faster and virtually indistinguishable.
int r = in[3] - (((256 - in[0])*in[3]) >> 8);
int g = in[3] - (((256 - in[1])*in[3]) >> 8);
int b = in[3] - (((256 - in[2])*in[3]) >> 8);
*out = ImageHelpers::RGBToPalette(doalpha, r, g, b);
out += Height;
in += 4;
}
break;
case JCS_YCbCr:
// Probably useless but since I had the formula available...
for (int x = Width; x > 0; --x)
{
double Y = in[0], Cb = in[1], Cr = in[2];
int r = clamp((int)(Y + 1.40200 * (Cr - 0x80)), 0, 255);
int g = clamp((int)(Y - 0.34414 * (Cb - 0x80) - 0.71414 * (Cr - 0x80)), 0, 255);
int b = clamp((int)(Y + 1.77200 * (Cb - 0x80)), 0, 255);
*out = ImageHelpers::RGBToPalette(doalpha, r, g, b);
out += Height;
in += 4;
}
break;
case JCS_YCCK:
// Probably useless but since I had the formula available...
for (int x = Width; x > 0; --x)
{
double Y = in[0], Cb = in[1], Cr = in[2];
int K = in[3];
int r = clamp((int)(Y + 1.40200 * (Cr - 0x80)), 0, 255);
int g = clamp((int)(Y - 0.34414 * (Cb - 0x80) - 0.71414 * (Cr - 0x80)), 0, 255);
int b = clamp((int)(Y + 1.77200 * (Cb - 0x80)), 0, 255);
r = r - ((r * K) >> 8);
g = g - ((g * K) >> 8);
b = b - ((b * K) >> 8);
*out = ImageHelpers::RGBToPalette(doalpha, r, g, b);
out += Height;
in += 4;
}
break;
default:
// The other colorspaces were considered above and discarded,
// but GCC will complain without a default for them here.
break;
}
y++;
}
jpeg_finish_decompress(&cinfo);
}
}
catch (int)
{
Printf(TEXTCOLOR_ORANGE "JPEG error in %s\n", fileSystem.GetFileFullPath(SourceLump).GetChars());
}
jpeg_destroy_decompress(&cinfo);
if (buff != NULL)
{
delete[] buff;
}
return Pixels;
}
//===========================================================================
//
// FJPEGTexture::CopyPixels
//
// Preserves the full color information (unlike software mode)
//
//===========================================================================
int FJPEGTexture::CopyPixels(FBitmap *bmp, int conversion)
{
PalEntry pe[256];
auto lump = fileSystem.OpenFileReader (SourceLump);
jpeg_decompress_struct cinfo;
jpeg_error_mgr jerr;
cinfo.err = jpeg_std_error(&jerr);
cinfo.err->output_message = JPEG_OutputMessage;
cinfo.err->error_exit = JPEG_ErrorExit;
jpeg_create_decompress(&cinfo);
FLumpSourceMgr sourcemgr(&lump, &cinfo);
try
{
jpeg_read_header(&cinfo, TRUE);
if (!((cinfo.out_color_space == JCS_RGB && cinfo.num_components == 3) ||
(cinfo.out_color_space == JCS_CMYK && cinfo.num_components == 4) ||
(cinfo.out_color_space == JCS_YCCK && cinfo.num_components == 4) ||
(cinfo.out_color_space == JCS_YCbCr && cinfo.num_components == 3) ||
(cinfo.out_color_space == JCS_GRAYSCALE && cinfo.num_components == 1)))
{
Printf(TEXTCOLOR_ORANGE "Unsupported color format in %s\n", fileSystem.GetFileFullPath(SourceLump).GetChars());
}
else
{
jpeg_start_decompress(&cinfo);
int yc = 0;
TArray<uint8_t> buff(cinfo.output_height * cinfo.output_width * cinfo.output_components, true);
while (cinfo.output_scanline < cinfo.output_height)
{
uint8_t * ptr = buff.Data() + cinfo.output_width * cinfo.output_components * yc;
jpeg_read_scanlines(&cinfo, &ptr, 1);
yc++;
}
switch (cinfo.out_color_space)
{
case JCS_RGB:
bmp->CopyPixelDataRGB(0, 0, buff.Data(), cinfo.output_width, cinfo.output_height,
3, cinfo.output_width * cinfo.output_components, 0, CF_RGB);
break;
case JCS_GRAYSCALE:
for (int i = 0; i < 256; i++) pe[i] = PalEntry(255, i, i, i); // default to a gray map
bmp->CopyPixelData(0, 0, buff.Data(), cinfo.output_width, cinfo.output_height,
1, cinfo.output_width, 0, pe);
break;
case JCS_CMYK:
bmp->CopyPixelDataRGB(0, 0, buff.Data(), cinfo.output_width, cinfo.output_height,
4, cinfo.output_width * cinfo.output_components, 0, CF_CMYK);
break;
case JCS_YCCK:
bmp->CopyPixelDataRGB(0, 0, buff.Data(), cinfo.output_width, cinfo.output_height,
4, cinfo.output_width * cinfo.output_components, 0, CF_YCCK);
break;
case JCS_YCbCr:
bmp->CopyPixelDataRGB(0, 0, buff.Data(), cinfo.output_width, cinfo.output_height,
4, cinfo.output_width * cinfo.output_components, 0, CF_YCbCr);
break;
default:
assert(0);
break;
}
jpeg_finish_decompress(&cinfo);
}
}
catch (int)
{
Printf(TEXTCOLOR_ORANGE "JPEG error in %s\n", fileSystem.GetFileFullPath(SourceLump).GetChars());
}
jpeg_destroy_decompress(&cinfo);
return 0;
}