786 lines
21 KiB
C
Raw Normal View History

2020-01-31 15:43:42 +01:00
/* Copyright (C) 2010-2020 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (rbmp.c).
* ---------------------------------------------------------------------------------------
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/* Modified version of stb_image's BMP sources. */
#include <stdio.h>
#include <stdint.h>
#include <stdarg.h>
#include <stddef.h> /* ptrdiff_t on osx */
#include <stdlib.h>
#include <string.h>
#include <retro_inline.h>
#include <formats/image.h>
#include <formats/rbmp.h>
/* truncate int to byte without warnings */
2019-09-18 19:52:45 +02:00
#define RBMP_BYTECAST(x) ((unsigned char) ((x) & 255))
#define RBMP_COMPUTE_Y(r, g, b) ((unsigned char) ((((r) * 77) + ((g) * 150) + (29 * (b))) >> 8))
typedef struct
{
2020-08-16 00:13:16 +02:00
unsigned char *img_buffer;
unsigned char *img_buffer_end;
unsigned char *img_buffer_original;
int img_n;
int img_out_n;
int buflen;
2020-08-16 00:13:16 +02:00
uint32_t img_x;
uint32_t img_y;
unsigned char buffer_start[128];
2019-09-18 19:52:45 +02:00
} rbmp_context;
struct rbmp
{
uint8_t *buff_data;
uint32_t *output_image;
};
2019-09-18 19:52:45 +02:00
static INLINE unsigned char rbmp_get8(rbmp_context *s)
{
if (s->img_buffer < s->img_buffer_end)
return *s->img_buffer++;
return 0;
}
2019-09-18 19:52:45 +02:00
static void rbmp_skip(rbmp_context *s, int n)
{
if (n < 0)
{
s->img_buffer = s->img_buffer_end;
return;
}
s->img_buffer += n;
}
2019-09-18 19:52:45 +02:00
static int rbmp_get16le(rbmp_context *s)
{
2019-09-18 19:52:45 +02:00
return rbmp_get8(s) + (rbmp_get8(s) << 8);
}
2019-09-18 20:25:15 +02:00
#define RBMP_GET32LE(s) (rbmp_get16le(s) + (rbmp_get16le(s) << 16))
2019-09-18 19:52:45 +02:00
static unsigned char *rbmp_convert_format(
unsigned char *data,
int img_n,
int req_comp,
unsigned int x,
unsigned int y)
{
int i,j;
unsigned char *good = (unsigned char *)malloc(req_comp * x * y);
if (!good)
return NULL;
for (j=0; j < (int) y; ++j)
{
unsigned char *src = data + j * x * img_n ;
unsigned char *dest = good + j * x * req_comp;
switch (((img_n)*8+(req_comp)))
{
case 10:
for (i = x-1; i >= 0; --i, src += 1, dest += 2)
{
dest[0]=src[0];
dest[1]=255;
}
break;
case 11:
for (i = x-1; i >= 0; --i, src += 1, dest += 3)
dest[0]=dest[1]=dest[2]=src[0];
break;
case 12:
for (i = x-1; i >= 0; --i, src += 1, dest += 4)
{
dest[0]=dest[1]=dest[2]=src[0];
dest[3]=255;
}
break;
case 17:
for (i = x-1; i >= 0; --i, src += 2, dest += 1)
dest[0]=src[0];
break;
case 19:
for (i = x-1; i >= 0; --i, src += 2, dest += 3)
dest[0]=dest[1]=dest[2]=src[0];
break;
case 20:
for (i = x-1; i >= 0; --i, src += 2, dest += 4)
{
dest[0]=dest[1]=dest[2]=src[0];
dest[3]=src[1];
}
break;
case 28:
for (i = x-1; i >= 0; --i, src += 3, dest += 4)
{
dest[0]=src[0];
dest[1]=src[1];
dest[2]=src[2];
dest[3]=255;
}
break;
case 25:
for (i = x-1; i >= 0; --i, src += 3, dest += 1)
dest[0] = RBMP_COMPUTE_Y(src[0],src[1],src[2]);
break;
case 26:
for (i = x-1; i >= 0; --i, src += 3, dest += 2)
{
dest[0] = RBMP_COMPUTE_Y(src[0],src[1],src[2]);
dest[1] = 255;
}
break;
case 33:
for (i = x-1; i >= 0; --i, src += 4, dest += 1)
dest[0] = RBMP_COMPUTE_Y(src[0],src[1],src[2]);
break;
case 34:
for (i = x-1; i >= 0; --i, src += 4, dest += 2)
{
dest[0] = RBMP_COMPUTE_Y(src[0],src[1],src[2]);
dest[1] = src[3];
}
break;
case 35:
for (i = x-1; i >= 0; --i, src += 4, dest += 3)
{
dest[0]=src[0];
dest[1]=src[1];
dest[2]=src[2];
}
break;
default:
break;
}
}
return good;
}
/* Microsoft/Windows BMP image */
/* returns 0..31 for the highest set bit */
2019-09-18 19:52:45 +02:00
static int rbmp_high_bit(unsigned int z)
{
int n=0;
if (z == 0)
return -1;
2019-09-18 19:52:45 +02:00
if (z >= 0x10000)
{
2019-09-18 19:52:45 +02:00
n += 16;
z >>= 16;
}
if (z >= 0x00100)
{
2019-09-18 19:52:45 +02:00
n += 8;
z >>= 8;
}
if (z >= 0x00010)
{
2019-09-18 19:52:45 +02:00
n += 4;
z >>= 4;
}
if (z >= 0x00004)
{
2019-09-18 19:52:45 +02:00
n += 2;
z >>= 2;
}
if (z >= 0x00002)
n += 1;
return n;
}
2019-09-18 19:52:45 +02:00
static int rbmp_bitcount(unsigned int a)
{
a = (a & 0x55555555) + ((a >> 1) & 0x55555555); /* max 2 */
a = (a & 0x33333333) + ((a >> 2) & 0x33333333); /* max 4 */
a = (a + (a >> 4)) & 0x0f0f0f0f; /* max 8 per 4, now 8 bits */
a = (a + (a >> 8)); /* max 16 per 8 bits */
a = (a + (a >> 16)); /* max 32 per 8 bits */
return a & 0xff;
}
2019-09-18 19:52:45 +02:00
static int rbmp_shiftsigned(int v, int shift, int bits)
{
int result;
2019-09-18 19:52:45 +02:00
int z = bits;
if (shift < 0)
v <<= -shift;
else
v >>= shift;
result = v;
while (z < 8)
{
result += v >> z;
2019-09-18 19:52:45 +02:00
z += bits;
}
return result;
}
2019-09-18 19:52:45 +02:00
static unsigned char *rbmp_bmp_load(rbmp_context *s, unsigned *x, unsigned *y,
int *comp, int req_comp)
{
unsigned char *out;
int bpp, flip_vertically, pad, target, offset, hsz;
int psize=0,i,j,width;
unsigned int mr=0,mg=0,mb=0,ma=0;
/* Corrupt BMP? */
2019-09-18 19:52:45 +02:00
if (rbmp_get8(s) != 'B' || rbmp_get8(s) != 'M')
return 0;
2019-09-18 20:25:15 +02:00
/* discard filesize */
rbmp_get16le(s);
rbmp_get16le(s);
/* discard reserved */
rbmp_get16le(s);
rbmp_get16le(s);
offset = (uint32_t)RBMP_GET32LE(s);
hsz = (uint32_t)RBMP_GET32LE(s);
/* BMP type not supported? */
if (hsz != 12 && hsz != 40 && hsz != 56 && hsz != 108 && hsz != 124)
return 0;
if (hsz == 12)
{
2019-09-18 19:52:45 +02:00
s->img_x = rbmp_get16le(s);
s->img_y = rbmp_get16le(s);
}
else
{
2019-09-18 20:25:15 +02:00
s->img_x = (uint32_t)RBMP_GET32LE(s);
s->img_y = (uint32_t)RBMP_GET32LE(s);
}
/* Bad BMP? */
2019-09-18 19:52:45 +02:00
if (rbmp_get16le(s) != 1)
return 0;
2019-09-18 19:52:45 +02:00
bpp = rbmp_get16le(s);
/* BMP 1-bit type not supported? */
if (bpp == 1)
return 0;
flip_vertically = ((int) s->img_y) > 0;
s->img_y = abs((int) s->img_y);
if (hsz == 12)
{
if (bpp < 24)
psize = (offset - 14 - 24) / 3;
}
else
{
2019-09-18 20:25:15 +02:00
int compress = (uint32_t)RBMP_GET32LE(s);
/* BMP RLE type not supported? */
if (compress == 1 || compress == 2)
return 0;
/* discard sizeof */
2019-09-18 20:25:15 +02:00
rbmp_get16le(s);
rbmp_get16le(s);
/* discard hres */
rbmp_get16le(s);
rbmp_get16le(s);
/* discard vres */
rbmp_get16le(s);
rbmp_get16le(s);
/* discard colors used */
rbmp_get16le(s);
rbmp_get16le(s);
/* discard max important */
rbmp_get16le(s);
rbmp_get16le(s);
if (hsz == 40 || hsz == 56)
{
if (hsz == 56)
{
2019-09-18 20:25:15 +02:00
rbmp_get16le(s);
rbmp_get16le(s);
rbmp_get16le(s);
rbmp_get16le(s);
rbmp_get16le(s);
rbmp_get16le(s);
rbmp_get16le(s);
rbmp_get16le(s);
}
if (bpp == 16 || bpp == 32)
{
switch (compress)
{
case 0:
#if 0
if (bpp == 32)
{
mr = 0xffu << 16;
mg = 0xffu << 8;
mb = 0xffu << 0;
ma = 0xffu << 24;
}
else
{
mr = 31u << 10;
mg = 31u << 5;
mb = 31u << 0;
}
#endif
break;
case 3:
2019-09-18 20:25:15 +02:00
mr = (uint32_t)RBMP_GET32LE(s);
mg = (uint32_t)RBMP_GET32LE(s);
mb = (uint32_t)RBMP_GET32LE(s);
/* not documented, but generated by
* Photoshop and handled by MS Paint */
/* Bad BMP ?*/
if (mr == mg && mg == mb)
return 0;
break;
default:
#if 0
mr = mg = mb = 0;
#endif
break;
}
/* Bad BMP? */
return 0;
}
}
else
{
2019-09-18 20:25:15 +02:00
mr = (uint32_t)RBMP_GET32LE(s);
mg = (uint32_t)RBMP_GET32LE(s);
mb = (uint32_t)RBMP_GET32LE(s);
ma = (uint32_t)RBMP_GET32LE(s);
/* Discard color space */
rbmp_get16le(s);
rbmp_get16le(s);
2019-09-18 19:52:45 +02:00
for (i = 0; i < 12; ++i)
2019-09-18 20:25:15 +02:00
{
/* Discard color space parameters */
rbmp_get16le(s);
rbmp_get16le(s);
}
if (hsz == 124)
{
2019-09-18 20:25:15 +02:00
/* Discard rendering intent */
rbmp_get16le(s);
rbmp_get16le(s);
/* Discard offset of profile data */
rbmp_get16le(s);
rbmp_get16le(s);
/* Discard size of profile data */
rbmp_get16le(s);
rbmp_get16le(s);
/* Discard reserved */
rbmp_get16le(s);
rbmp_get16le(s);
}
}
if (bpp < 16)
psize = (offset - 14 - hsz) >> 2;
}
s->img_n = ma ? 4 : 3;
if (req_comp && req_comp >= 3) /* We can directly decode 3 or 4 */
target = req_comp;
else
target = s->img_n; /* If they want monochrome, we'll post-convert */
out = (unsigned char *) malloc(target * s->img_x * s->img_y);
if (!out)
return 0;
if (bpp < 16)
{
unsigned char pal[256][4];
int z=0;
/* Corrupt BMP? */
if (psize == 0 || psize > 256)
{
free(out);
return 0;
}
2019-09-18 19:52:45 +02:00
for (i = 0; i < psize; ++i)
{
2019-09-18 19:52:45 +02:00
pal[i][2] = rbmp_get8(s);
pal[i][1] = rbmp_get8(s);
pal[i][0] = rbmp_get8(s);
if (hsz != 12)
rbmp_get8(s);
pal[i][3] = 255;
}
2019-09-18 19:52:45 +02:00
rbmp_skip(s, offset - 14 - hsz - psize * (hsz == 12 ? 3 : 4));
if (bpp == 4)
width = (s->img_x + 1) >> 1;
else if (bpp == 8)
width = s->img_x;
else
{
/* Corrupt BMP */
free(out);
return 0;
}
pad = (-width)&3;
for (j=0; j < (int) s->img_y; ++j)
{
2019-09-18 19:52:45 +02:00
for (i = 0; i < (int) s->img_x; i += 2)
{
2019-09-18 19:52:45 +02:00
int v = rbmp_get8(s);
int v2 = 0;
if (bpp == 4)
{
2019-09-18 19:52:45 +02:00
v2 = v & 15;
v >>= 4;
}
out[z++] = pal[v][0];
out[z++] = pal[v][1];
out[z++] = pal[v][2];
if (target == 4)
out[z++] = 255;
if (i+1 == (int)s->img_x)
break;
2019-09-18 19:52:45 +02:00
v = (bpp == 8) ? rbmp_get8(s) : v2;
out[z++] = pal[v][0];
out[z++] = pal[v][1];
out[z++] = pal[v][2];
if (target == 4)
out[z++] = 255;
}
2019-09-18 19:52:45 +02:00
rbmp_skip(s, pad);
}
}
else
{
2019-09-18 19:52:45 +02:00
int rshift = 0;
int gshift = 0;
int bshift = 0;
int ashift = 0;
int rcount = 0;
int gcount = 0;
int bcount = 0;
int acount = 0;
int z = 0;
int easy = 0;
rbmp_skip(s, offset - 14 - hsz);
if (bpp == 24)
width = 3 * s->img_x;
else if (bpp == 16)
width = 2*s->img_x;
else /* bpp = 32 and pad = 0 */
width=0;
pad = (-width) & 3;
switch (bpp)
{
case 24:
easy = 1;
break;
case 32:
if (mb == 0xff && mg == 0xff00 && mr == 0x00ff0000 && ma == 0xff000000)
easy = 2;
break;
default:
break;
}
if (!easy)
{
/* Corrupt BMP? */
if (!mr || !mg || !mb)
{
free(out);
return 0;
}
/* right shift amt to put high bit in position #7 */
2019-09-18 19:52:45 +02:00
rshift = rbmp_high_bit(mr)-7;
rcount = rbmp_bitcount(mr);
gshift = rbmp_high_bit(mg)-7;
gcount = rbmp_bitcount(mg);
bshift = rbmp_high_bit(mb)-7;
bcount = rbmp_bitcount(mb);
ashift = rbmp_high_bit(ma)-7;
acount = rbmp_bitcount(ma);
}
2019-09-18 19:52:45 +02:00
for (j=0; j < (int) s->img_y; ++j)
{
if (easy)
{
2019-09-18 19:52:45 +02:00
if (target == 4)
{
/* Need to apply alpha channel as well */
if (easy == 2)
{
for (i = 0; i < (int) s->img_x; ++i)
{
out[z+2] = rbmp_get8(s);
out[z+1] = rbmp_get8(s);
out[z+0] = rbmp_get8(s);
z += 3;
out[z++] = rbmp_get8(s);
}
}
else
{
for (i = 0; i < (int) s->img_x; ++i)
{
out[z+2] = rbmp_get8(s);
out[z+1] = rbmp_get8(s);
out[z+0] = rbmp_get8(s);
z += 3;
out[z++] = 255;
}
}
}
else
{
2019-09-18 19:52:45 +02:00
for (i = 0; i < (int) s->img_x; ++i)
{
out[z+2] = rbmp_get8(s);
out[z+1] = rbmp_get8(s);
out[z+0] = rbmp_get8(s);
z += 3;
}
}
}
else
{
2019-09-18 19:52:45 +02:00
if (target == 4)
{
2019-09-18 19:52:45 +02:00
/* Need to apply alpha channel as well */
if (ma)
{
2019-09-18 20:25:15 +02:00
if (bpp == 16)
2019-09-18 19:52:45 +02:00
{
2019-09-18 20:25:15 +02:00
for (i = 0; i < (int) s->img_x; ++i)
{
uint32_t v = (uint32_t)rbmp_get16le(s);
out[z++] = RBMP_BYTECAST(rbmp_shiftsigned(v & mr, rshift, rcount));
out[z++] = RBMP_BYTECAST(rbmp_shiftsigned(v & mg, gshift, gcount));
out[z++] = RBMP_BYTECAST(rbmp_shiftsigned(v & mb, bshift, bcount));
out[z++] = RBMP_BYTECAST(rbmp_shiftsigned(v & ma, ashift, acount));
}
}
else
{
for (i = 0; i < (int) s->img_x; ++i)
{
uint32_t v = (uint32_t)RBMP_GET32LE(s);
out[z++] = RBMP_BYTECAST(rbmp_shiftsigned(v & mr, rshift, rcount));
out[z++] = RBMP_BYTECAST(rbmp_shiftsigned(v & mg, gshift, gcount));
out[z++] = RBMP_BYTECAST(rbmp_shiftsigned(v & mb, bshift, bcount));
out[z++] = RBMP_BYTECAST(rbmp_shiftsigned(v & ma, ashift, acount));
}
2019-09-18 19:52:45 +02:00
}
}
else
{
2019-09-18 20:25:15 +02:00
if (bpp == 16)
2019-09-18 19:52:45 +02:00
{
2019-09-18 20:25:15 +02:00
for (i = 0; i < (int) s->img_x; ++i)
{
uint32_t v = (uint32_t)rbmp_get16le(s);
out[z++] = RBMP_BYTECAST(rbmp_shiftsigned(v & mr, rshift, rcount));
out[z++] = RBMP_BYTECAST(rbmp_shiftsigned(v & mg, gshift, gcount));
out[z++] = RBMP_BYTECAST(rbmp_shiftsigned(v & mb, bshift, bcount));
out[z++] = RBMP_BYTECAST(255);
}
}
else
{
for (i = 0; i < (int) s->img_x; ++i)
{
uint32_t v = (uint32_t)RBMP_GET32LE(s);
out[z++] = RBMP_BYTECAST(rbmp_shiftsigned(v & mr, rshift, rcount));
out[z++] = RBMP_BYTECAST(rbmp_shiftsigned(v & mg, gshift, gcount));
out[z++] = RBMP_BYTECAST(rbmp_shiftsigned(v & mb, bshift, bcount));
out[z++] = RBMP_BYTECAST(255);
}
2019-09-18 19:52:45 +02:00
}
}
}
else
{
if (bpp == 16)
{
for (i = 0; i < (int) s->img_x; ++i)
{
uint32_t v = (uint32_t)rbmp_get16le(s);
out[z++] = RBMP_BYTECAST(rbmp_shiftsigned(v & mr, rshift, rcount));
out[z++] = RBMP_BYTECAST(rbmp_shiftsigned(v & mg, gshift, gcount));
out[z++] = RBMP_BYTECAST(rbmp_shiftsigned(v & mb, bshift, bcount));
}
}
else
{
for (i = 0; i < (int) s->img_x; ++i)
{
2019-09-18 20:25:15 +02:00
uint32_t v = (uint32_t)RBMP_GET32LE(s);
2019-09-18 19:52:45 +02:00
out[z++] = RBMP_BYTECAST(rbmp_shiftsigned(v & mr, rshift, rcount));
out[z++] = RBMP_BYTECAST(rbmp_shiftsigned(v & mg, gshift, gcount));
out[z++] = RBMP_BYTECAST(rbmp_shiftsigned(v & mb, bshift, bcount));
}
}
}
}
2019-09-18 19:52:45 +02:00
rbmp_skip(s, pad);
}
}
if (flip_vertically)
{
unsigned char t;
for (j=0; j < (int) s->img_y>>1; ++j)
{
unsigned char *p1 = out + j *s->img_x*target;
unsigned char *p2 = out + (s->img_y-1-j)*s->img_x*target;
2019-09-18 19:52:45 +02:00
for (i = 0; i < (int) s->img_x*target; ++i)
{
t = p1[i];
p1[i] = p2[i];
p2[i] = t;
}
}
}
if (
req_comp
&& (req_comp >= 1 && req_comp <= 4)
&& (req_comp != target))
{
2019-09-18 19:52:45 +02:00
unsigned char *tmp = rbmp_convert_format(out, target, req_comp, s->img_x, s->img_y);
free(out);
out = NULL;
if (!tmp)
return NULL;
out = tmp;
}
*x = s->img_x;
*y = s->img_y;
if (comp)
*comp = s->img_n;
return out;
}
static unsigned char *rbmp_load_from_memory(unsigned char const *buffer, int len,
unsigned *x, unsigned *y, int *comp, int req_comp)
{
2019-09-18 19:52:45 +02:00
rbmp_context s;
s.img_buffer = (unsigned char*)buffer;
s.img_buffer_original = (unsigned char*)buffer;
s.img_buffer_end = (unsigned char*)buffer+len;
2019-09-18 19:52:45 +02:00
return rbmp_bmp_load(&s,x,y,comp,req_comp);
}
static void rbmp_convert_frame(uint32_t *frame, unsigned width, unsigned height)
{
uint32_t *end = frame + (width * height * sizeof(uint32_t))/4;
while (frame < end)
{
uint32_t pixel = *frame;
*frame = (pixel & 0xff00ff00) | ((pixel << 16) & 0x00ff0000) | ((pixel >> 16) & 0xff);
frame++;
}
}
int rbmp_process_image(rbmp_t *rbmp, void **buf_data,
size_t size, unsigned *width, unsigned *height)
{
int comp;
if (!rbmp)
return IMAGE_PROCESS_ERROR;
rbmp->output_image = (uint32_t*)rbmp_load_from_memory(rbmp->buff_data,
(int)size, width, height, &comp, 4);
*buf_data = rbmp->output_image;
rbmp_convert_frame(rbmp->output_image, *width, *height);
return IMAGE_PROCESS_END;
}
bool rbmp_set_buf_ptr(rbmp_t *rbmp, void *data)
{
if (!rbmp)
return false;
rbmp->buff_data = (uint8_t*)data;
return true;
}
void rbmp_free(rbmp_t *rbmp)
{
if (!rbmp)
return;
free(rbmp);
}
rbmp_t *rbmp_alloc(void)
{
rbmp_t *rbmp = (rbmp_t*)calloc(1, sizeof(*rbmp));
if (!rbmp)
return NULL;
return rbmp;
}