1 /*
2 * Copyright 2016-2021 Esri
3 *
4 * Author: Lucian Plesea
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18 
19 /*
20 * JPNG band, uses JPEG or PNG encoding, depending on the input data
21 */
22 
23 #include "marfa.h"
24 
25 CPL_C_START
26 #include <jpeglib.h>
27 #include <png.h>
28 CPL_C_END
29 
30 NAMESPACE_MRF_START
31 
32 // Test that all alpha values are equal to N
AllAlpha(const buf_mgr & src,const ILImage & img)33 template<int N> static bool AllAlpha(const buf_mgr &src, const ILImage &img) {
34     int stride = img.pagesize.c;
35     char *s = src.buffer + img.pagesize.c - 1;
36     char *stop = src.buffer + img.pageSizeBytes;
37     while (s < stop && N == static_cast<unsigned char>(*s))
38         s += stride;
39     return s >= stop;
40 }
41 
42 // Strip the alpha from an RGBA buffer, safe to use in place
RGBA2RGB(const char * start,const char * stop,char * target)43 static void RGBA2RGB(const char *start, const char *stop, char *target) {
44     while (start < stop) {
45         *target++ = *start++;
46         *target++ = *start++;
47         *target++ = *start++;
48         start++; // Skip the alpha
49     }
50 }
51 
52 // Add opaque alpha to an RGB buffer, safe to use in place
53 // works from stop to start, the last parameter is the end of the source region
RGB2RGBA(const char * start,char * stop,const char * source_end)54 static void RGB2RGBA(const char *start, char *stop, const char *source_end) {
55     while (start < stop) {
56         *--stop = ~static_cast<char>(0);
57         *--stop = *--source_end;
58         *--stop = *--source_end;
59         *--stop = *--source_end;
60     }
61 }
62 
63 // Strip the alpha from an Luma Alpha buffer, safe to use in place
LA2L(const char * start,const char * stop,char * target)64 static void LA2L(const char *start, const char *stop, char *target) {
65     while (start < stop) {
66         *target++ = *start++;
67         start++; // Skip the alpha
68     }
69 }
70 
71 // Add opaque alpha to a Luma buffer, safe to use in place
72 // works from stop to start, the last parameter is the end of the source region
L2LA(const char * start,char * stop,const char * source_end)73 static void L2LA(const char *start, char *stop, const char *source_end) {
74     while (start < stop) {
75         *--stop = ~static_cast<char>(0);
76         *--stop = *--source_end;
77     }
78 }
79 
initBuffer(buf_mgr & b)80 static CPLErr initBuffer(buf_mgr &b) {
81     b.buffer = (char *)(CPLMalloc(b.size));
82     if (b.buffer != nullptr)
83         return CE_None;
84     CPLError(CE_Failure, CPLE_OutOfMemory, "Allocating temporary JPNG buffer");
85     return CE_Failure;
86 }
87 
Decompress(buf_mgr & dst,buf_mgr & src)88 CPLErr JPNG_Band::Decompress(buf_mgr &dst, buf_mgr &src) {
89     const static GUInt32 JPEG_SIG = 0xe0ffd8ff; // JPEG 4CC code
90     const static GUInt32 PNG_SIG  = 0x474e5089;  // PNG 4CC code
91 
92     CPLErr retval = CE_None;
93     ILImage image(img);
94     GUInt32 signature;
95     memcpy(&signature, src.buffer, sizeof(GUInt32));
96 
97     // test against an LSB signature
98     if (JPEG_SIG == CPL_LSBWORD32(signature)) {
99         image.pagesize.c -= 1;
100         JPEG_Codec codec(image);
101 
102         // JPEG decoder expects the destination size to be accurate
103         buf_mgr temp = dst; // dst still owns the storage
104         temp.size = (image.pagesize.c == 3) ? dst.size / 4 * 3 : dst.size / 2;
105 
106         retval = codec.DecompressJPEG(temp, src);
107         if (CE_None == retval) { // add opaque alpha, in place
108             if (image.pagesize.c == 3)
109                 RGB2RGBA(dst.buffer, dst.buffer + dst.size, temp.buffer + temp.size);
110             else
111                 L2LA(dst.buffer, dst.buffer + dst.size, temp.buffer + temp.size);
112         }
113     }
114     else if (PNG_SIG == CPL_LSBWORD32(signature)) { // Should be PNG, it reads as 4 bands
115         PNG_Codec codec(image);
116         return codec.DecompressPNG(dst, src);
117     }
118     else {
119         CPLError(CE_Failure, CPLE_NotSupported, "Not a JPEG or PNG tile");
120         retval = CE_Failure;
121     }
122 
123     return retval;
124 }
125 
126 // The PNG internal palette is set on first band write
Compress(buf_mgr & dst,buf_mgr & src)127 CPLErr JPNG_Band::Compress(buf_mgr &dst, buf_mgr &src) {
128     ILImage image(img);
129     buf_mgr temp = { nullptr, static_cast<size_t>(img.pageSizeBytes) };
130     CPLErr retval = initBuffer(temp);
131     if (retval != CE_None)
132         return retval;
133 
134     if (AllAlpha<255>(src, image)) { // If all pixels are opaque, compress as JPEG
135         if (image.pagesize.c == 4)
136             RGBA2RGB(src.buffer, src.buffer + src.size, temp.buffer);
137         else
138             LA2L(src.buffer, src.buffer + src.size, temp.buffer);
139 
140         image.pagesize.c -= 1; // RGB or Grayscale only for JPEG
141         JPEG_Codec codec(image);
142         codec.rgb = rgb;
143         codec.optimize = optimize;
144         codec.sameres = sameres;
145         retval = codec.CompressJPEG(dst, temp);
146     }
147     else if (!AllAlpha<0>(src, image)) {
148         PNG_Codec codec(image);
149         codec.deflate_flags = deflate_flags;
150         retval = codec.CompressPNG(dst, src);
151     }
152     else dst.size = 0; // Don't store fully transparent pages
153 
154     CPLFree(temp.buffer);
155     return retval;
156 }
157 
158 /**
159 * \brief For PPNG, builds the data structures needed to write the palette
160 * The presence of the PNGColors and PNGAlpha is used as a flag for PPNG only
161 */
162 
JPNG_Band(MRFDataset * pDS,const ILImage & image,int b,int level)163 JPNG_Band::JPNG_Band( MRFDataset *pDS, const ILImage &image,
164                       int b, int level ) :
165     MRFRasterBand(pDS, image, b, level),
166     rgb(FALSE),
167     sameres(FALSE),
168     optimize(false)
169 {   // Check error conditions
170     if (image.dt != GDT_Byte) {
171         CPLError(CE_Failure, CPLE_NotSupported, "Data type not supported by MRF JPNG");
172         return;
173     }
174     if (image.order != IL_Interleaved || (image.pagesize.c != 4 && image.pagesize.c != 2)) {
175         CPLError(CE_Failure, CPLE_NotSupported, "MRF JPNG can only handle 2 or 4 interleaved bands");
176         return;
177     }
178 
179     if (img.pagesize.c == 4) { // RGBA can have storage flavors
180         CPLString const &pm = pDS->GetPhotometricInterpretation();
181         if (pm == "RGB" || pm == "MULTISPECTRAL") { // Explicit RGB or MS
182             rgb = TRUE;
183             sameres = TRUE;
184         }
185         if (pm == "YCC")
186             sameres = TRUE;
187     }
188 
189     optimize = GetOptlist().FetchBoolean("OPTIMIZE", FALSE) != FALSE;
190 
191     // PNGs and JPGs can be larger than the source, especially for
192     // small page size.
193     poDS->SetPBufferSize(image.pageSizeBytes + 100);
194 }
195 
~JPNG_Band()196 JPNG_Band::~JPNG_Band() {}
197 
198 NAMESPACE_MRF_END
199