Skip to content

Commit fddf616

Browse files
committed
Add plMipmap::CompressJPEG().
This adds an easy way to actually cause mipmaps with JPEG compression to actually its content. The implementation largely mirrors what Cyan does... If RLE can get the image down below 5KB, then don't compress. Otherwise, compress to jpeg. This is especially useful for Python apps where plJPEG::CompressImage() still doesn't have bindings.
1 parent 41732c2 commit fddf616

9 files changed

Lines changed: 184 additions & 3 deletions

File tree

Python/PRP/Surface/pyMipmap.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,26 @@ PY_METHOD_KWARGS(Mipmap, CompressImage,
308308
Py_RETURN_NONE;
309309
}
310310

311+
PY_METHOD_KWARGS(Mipmap, CompressJPEG,
312+
"Params: quality, force\n"
313+
"Compresses the mipmap to JPEG format")
314+
{
315+
static char* kwlist[] = { _pycs("quality"), _pycs("force"), nullptr };
316+
int quality = 70;
317+
int force = 0;
318+
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|ii", kwlist, &quality, &force)) {
319+
PyErr_SetString(PyExc_TypeError, "CompressJPEG expects an optional int and an optional boolean");
320+
return nullptr;
321+
}
322+
try {
323+
self->fThis->CompressJPEG(quality, force != 0);
324+
} catch (const hsBadParamException& ex) {
325+
PyErr_SetString(PyExc_RuntimeError, ex.what());
326+
return nullptr;
327+
}
328+
Py_RETURN_NONE;
329+
}
330+
311331
static PyMethodDef pyMipmap_Methods[] = {
312332
pyMipmap_readData_method,
313333
pyMipmap_writeData_method,
@@ -326,6 +346,7 @@ static PyMethodDef pyMipmap_Methods[] = {
326346
pyMipmap_isAlphaJPEG_method,
327347
pyMipmap_DecompressImage_method,
328348
pyMipmap_CompressImage_method,
349+
pyMipmap_CompressJPEG_method,
329350
PY_METHOD_TERMINATOR
330351
};
331352

Python/PyHSPlasma.pyi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4124,6 +4124,7 @@ class plMipmap(plBitmap):
41244124
def __init__(self, name: str = "") -> None: ...
41254125

41264126
def CompressImage(self, level: int, data: bytes, quality: int = kBlockQualityNormal) -> None: ...
4127+
def CompressJPEG(self, quality: int = 70, force: bool = False) -> None: ...
41274128
def DecompressImage(self, level: int) -> bytes: ...
41284129
def extractAlphaData(self) -> bytes: ...
41294130
def extractColorData(self) -> bytes: ...

core/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -656,6 +656,7 @@ set(SDL_HEADERS
656656

657657
set(STREAM_SOURCES
658658
Stream/hsElfStream.cpp
659+
Stream/hsNullStream.cpp
659660
Stream/hsRAMStream.cpp
660661
Stream/hsStdioStream.cpp
661662
Stream/hsStream.cpp
@@ -668,6 +669,7 @@ set(STREAM_SOURCES
668669
)
669670
set(STREAM_HEADERS
670671
Stream/hsElfStream.h
672+
Stream/hsNullStream.h
671673
Stream/hsRAMStream.h
672674
Stream/hsStdioStream.h
673675
Stream/hsStream.h

core/PRP/Surface/plMipmap.cpp

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,13 @@
1919
#include <algorithm>
2020
#include <cstring>
2121
#include <cstdlib>
22+
#include <memory>
2223

2324
#include "Debug/plDebug.h"
2425
#include "Util/plJPEG.h"
2526
#include "Util/plPNG.h"
2627
#include "Util/plDDSurface.h"
28+
#include "Stream/hsNullStream.h"
2729
#include "Stream/hsRAMStream.h"
2830
#include "3rdPartyLibs/squish/squish.h"
2931

@@ -771,6 +773,75 @@ void plMipmap::CompressImage(size_t level, void* src, size_t size, BlockQuality
771773
}
772774
}
773775

776+
void plMipmap::CompressJPEG(int quality, bool force)
777+
{
778+
if (fCompressionType != kJPEGCompression)
779+
throw hsBadParamException(__FILE__, __LINE__, "JPEG compression only supported on JPEG mipmaps");
780+
781+
// We don't want to recompress if we already have JPEG data.
782+
// That can cause data loss, artificating, and diff churn.
783+
// But, if they say force... go for it.
784+
bool compressColor = fJPEGCache.empty() || force;
785+
bool compressAlpha = fJAlphaCache.empty() || force;
786+
787+
if (!force) {
788+
{
789+
hsNullStream S;
790+
IWriteRLEImage(&S, false);
791+
compressColor = S.size() >= 5 * 1024;
792+
}
793+
{
794+
hsNullStream S;
795+
IWriteRLEImage(&S, true);
796+
compressAlpha = S.size() >= 5 * 1024;
797+
}
798+
}
799+
800+
if (compressColor) {
801+
auto colorBuf = std::make_unique <uint8_t[]>(fWidth * fHeight * 3);
802+
803+
// Guess what? plJPEG::CompressImage() expects RGB, not BGR.
804+
// Just roll our own conversion loop here.
805+
const unsigned char* sp = (const unsigned char*)fImageData;
806+
unsigned char* dp = (unsigned char*)colorBuf.get();
807+
for (size_t i = 0; i < fLevelData[0].fSize; i += 4) {
808+
unsigned char b = *sp++;
809+
unsigned char g = *sp++;
810+
unsigned char r = *sp++;
811+
sp++; // Skip alpha
812+
*dp++ = r;
813+
*dp++ = g;
814+
*dp++ = b;
815+
}
816+
817+
hsRAMStream S;
818+
plJPEG::CompressJPEG(&S, colorBuf.get(), fWidth * fHeight * 3, fWidth, fHeight, 24, quality);
819+
fJPEGCache.resize(S.size());
820+
memcpy(fJPEGCache.data(), S.data(), S.size());
821+
}
822+
823+
if (compressAlpha) {
824+
auto alphaBuf = std::make_unique <uint8_t[]>(fWidth * fHeight * 3);
825+
826+
// extractAlphaData only extracts 1 byte per pixel, but plJPEG expects 3 bytes per pixel.
827+
// Cyan's alpha JPEGs stuff the alpha byte into the red channel only.
828+
const unsigned char* sp = (const unsigned char*)fImageData;
829+
unsigned char* dp = (unsigned char*)alphaBuf.get();
830+
for (size_t i = 0; i < fLevelData[0].fSize; i += 4) {
831+
sp += 3; // Skip RGB
832+
*dp++ = *sp++; // Alpha (R)
833+
*dp++ = 0; // G
834+
*dp++ = 0; // B
835+
}
836+
837+
hsRAMStream S;
838+
// Cyan's alpha JPEGs are always 100% quality.
839+
plJPEG::CompressJPEG(&S, alphaBuf.get(), fWidth * fHeight * 3, fWidth, fHeight, 24, 100);
840+
fJAlphaCache.resize(S.size());
841+
memcpy(fJAlphaCache.data(), S.data(), S.size());
842+
}
843+
}
844+
774845
/* plLODMipmap */
775846
void plLODMipmap::read(hsStream* S, plResManager* mgr)
776847
{

core/PRP/Surface/plMipmap.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,14 @@ class HSPLASMA_EXPORT plMipmap : public plBitmap
112112
size_t GetUncompressedSize(size_t level) const;
113113
void DecompressImage(size_t level, void* dest, size_t size) const;
114114
void CompressImage(size_t level, void* src, size_t size, BlockQuality quality = kBlockQualityNormal);
115+
116+
/**
117+
* Compress mipmap to JPEG format.
118+
* This compresses the image data to JPEG with the specified quality.
119+
* If the mipmap will not benefit from JPEG compression (i.e. the RLE
120+
* map is under 5 KB), then no compression is done unless 'force' is true.
121+
*/
122+
void CompressJPEG(int quality = 70, bool force = false);
115123
};
116124

117125

core/Stream/hsNullStream.cpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/* This file is part of HSPlasma.
2+
*
3+
* HSPlasma is free software: you can redistribute it and/or modify
4+
* it under the terms of the GNU General Public License as published by
5+
* the Free Software Foundation, either version 3 of the License, or
6+
* (at your option) any later version.
7+
*
8+
* HSPlasma is distributed in the hope that it will be useful,
9+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
* GNU General Public License for more details.
12+
*
13+
* You should have received a copy of the GNU General Public License
14+
* along with HSPlasma. If not, see <http://www.gnu.org/licenses/>.
15+
*/
16+
17+
#include "hsNullStream.h"
18+
19+
size_t hsNullStream::read(size_t size, void*)
20+
{
21+
if (size + fPos > fSize)
22+
throw hsFileReadException(__FILE__, __LINE__, "Read past end of buffer");
23+
fPos += size;
24+
return size;
25+
}
26+
27+
size_t hsNullStream::write(size_t size, const void*)
28+
{
29+
fPos += size;
30+
if (fPos > fSize)
31+
fSize = fPos;
32+
return size;
33+
}

core/Stream/hsNullStream.h

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/* This file is part of HSPlasma.
2+
*
3+
* HSPlasma is free software: you can redistribute it and/or modify
4+
* it under the terms of the GNU General Public License as published by
5+
* the Free Software Foundation, either version 3 of the License, or
6+
* (at your option) any later version.
7+
*
8+
* HSPlasma is distributed in the hope that it will be useful,
9+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
* GNU General Public License for more details.
12+
*
13+
* You should have received a copy of the GNU General Public License
14+
* along with HSPlasma. If not, see <http://www.gnu.org/licenses/>.
15+
*/
16+
17+
#ifndef _HSNULLSTREAM_H
18+
#define _HSNULLSTREAM_H
19+
20+
#include "hsStream.h"
21+
22+
class HSPLASMA_EXPORT hsNullStream : public hsStream
23+
{
24+
protected:
25+
uint32_t fSize, fPos;
26+
27+
public:
28+
explicit hsNullStream(int pv = PlasmaVer::pvUnknown)
29+
: hsStream(pv), fSize(), fPos() { }
30+
31+
uint32_t size() const HS_OVERRIDE { return fSize; }
32+
uint32_t pos() const HS_OVERRIDE { return fPos; }
33+
bool eof() const HS_OVERRIDE { return (fPos >= fSize); }
34+
35+
void seek(uint32_t pos) HS_OVERRIDE { fPos = pos; }
36+
void skip(int32_t count) HS_OVERRIDE { fPos += count; }
37+
void fastForward() HS_OVERRIDE { fPos = fSize; }
38+
void rewind() HS_OVERRIDE { fPos = 0; }
39+
40+
size_t read(size_t size, void* buf) HS_OVERRIDE;
41+
size_t write(size_t size, const void* buf) HS_OVERRIDE;
42+
};
43+
44+
#endif

core/Util/plJPEG.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,7 @@ plMipmap* plJPEG::DecompressJPEG(hsStream* S)
330330
return newMipmap;
331331
}
332332

333-
void plJPEG::CompressJPEG(hsStream* S, void* buf, size_t size, uint32_t width, uint32_t height, uint32_t bpp)
333+
void plJPEG::CompressJPEG(hsStream* S, void* buf, size_t size, uint32_t width, uint32_t height, uint32_t bpp, int quality)
334334
{
335335
plJPEG_CInfo cinfo;
336336

@@ -347,7 +347,7 @@ void plJPEG::CompressJPEG(hsStream* S, void* buf, size_t size, uint32_t width, u
347347
cinfo->in_color_space = JCS_RGB;
348348

349349
jpeg_set_defaults(&cinfo);
350-
jpeg_set_quality(&cinfo, 100, TRUE);
350+
jpeg_set_quality(&cinfo, quality, TRUE);
351351
jpeg_start_compress(&cinfo, TRUE);
352352

353353
uint32_t row_stride = cinfo->image_width * cinfo->input_components;

core/Util/plJPEG.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ class HSPLASMA_EXPORT plJPEG
4747

4848
/* Write JPEG file to stream from bitmap data buffer. */
4949
static void CompressJPEG(hsStream* S, void* buf, size_t size,
50-
uint32_t width, uint32_t height, uint32_t bpp);
50+
uint32_t width, uint32_t height, uint32_t bpp,
51+
int quality = 100);
5152

5253
private:
5354
plJPEG() = delete;

0 commit comments

Comments
 (0)