25 #include "shared/shared.h"
26 #include "common/common.h"
27 #include "common/cvar.h"
28 #include "common/files.h"
29 #include "refresh/images.h"
30 #include "format/pcx.h"
31 #include "format/wal.h"
32 #include "stb_image.h"
33 #include "stb_image_write.h"
35 #define R_COLORMAP_PCX "pics/colormap.pcx"
38 static qerror_t IMG_Load##x(byte *rawdata, size_t rawlen, \
39 image_t *image, byte **pic)
42 static qerror_t IMG_Save##x(qhandle_t f, const char *filename, \
43 byte *pic, int width, int height, int row_stride, int param)
65 #define FLOODFILL_FIFO_SIZE 0x1000
66 #define FLOODFILL_FIFO_MASK (FLOODFILL_FIFO_SIZE - 1)
68 #define FLOODFILL_STEP(off, dx, dy) \
70 if (pos[off] == fillcolor) { \
72 fifo[inpt].x = x + (dx); \
73 fifo[inpt].y = y + (dy); \
74 inpt = (inpt + 1) & FLOODFILL_FIFO_MASK; \
75 } else if (pos[off] != 255) { \
89 byte fillcolor = *skin;
91 int inpt = 0, outpt = 0;
96 if (fillcolor == filledcolor || fillcolor == 255) {
100 fifo[inpt].
x = 0, fifo[inpt].
y = 0;
103 while (outpt != inpt) {
104 int x = fifo[outpt].
x, y = fifo[outpt].
y;
105 int fdc = filledcolor;
106 byte *pos = &skin[x + skinwidth * y];
115 skin[x + skinwidth * y] = fdc;
132 int x, y, w, h, scan;
133 int dataByte, runLength;
138 if (rawlen <
sizeof(dpcx_t)) {
139 return Q_ERR_FILE_TOO_SMALL;
142 pcx = (dpcx_t *)rawdata;
144 if (pcx->manufacturer != 10 || pcx->version != 5) {
145 return Q_ERR_UNKNOWN_FORMAT;
148 if (pcx->encoding != 1 || pcx->bits_per_pixel != 8) {
149 return Q_ERR_INVALID_FORMAT;
152 w = (LittleShort(pcx->xmax) - LittleShort(pcx->xmin)) + 1;
153 h = (LittleShort(pcx->ymax) - LittleShort(pcx->ymin)) + 1;
154 if (w < 1 || h < 1 || w > 640 || h > 480) {
155 return Q_ERR_INVALID_FORMAT;
158 if (pcx->color_planes != 1) {
159 return Q_ERR_INVALID_FORMAT;
162 scan = LittleShort(pcx->bytes_per_line);
164 return Q_ERR_INVALID_FORMAT;
172 return Q_ERR_FILE_TOO_SMALL;
174 memcpy(palette, (
byte *)pcx + rawlen - 768, 768);
182 end = (
byte *)pcx + rawlen;
183 for (y = 0; y < h; y++, pixels += w) {
184 for (x = 0; x < scan;) {
186 return Q_ERR_BAD_RLE_PACKET;
189 if ((dataByte & 0xC0) == 0xC0) {
190 runLength = dataByte & 0x3F;
191 if (x + runLength > scan)
192 return Q_ERR_BAD_RLE_PACKET;
194 return Q_ERR_BAD_RLE_PACKET;
200 while (runLength--) {
202 pixels[x] = dataByte;
214 return Q_ERR_SUCCESS;
225 qboolean has_alpha = qfalse;
227 for (y = 0; y <
height; y++) {
228 for (x = 0; x <
width; x++) {
234 if (y > 0 && *(in -
width) != 255)
238 else if (x > 0 && *(in - 1) != 255)
240 else if (x <
width - 1 && *(in + 1) != 255)
242 else if (y > 0 && x > 0 && *(in -
width - 1) != 255)
243 p = *(in -
width - 1);
244 else if (y > 0 && x <
width - 1 && *(in -
width + 1) != 255)
245 p = *(in -
width + 1);
246 else if (y < height - 1 && x > 0 && *(in +
width - 1) != 255)
247 p = *(in +
width - 1);
249 p = *(in +
width + 1);
263 return IF_PALETTED | IF_TRANSPARENT;
265 return IF_PALETTED | IF_OPAQUE;
270 byte buffer[640 * 480];
278 if (image->type == IT_SKIN)
281 *pic = IMG_AllocPixels(w * h * 4);
283 image->upload_width = image->width = w;
284 image->upload_height = image->height = h;
285 image->flags |=
IMG_Unpack8((uint32_t *)*pic, buffer, w, h);
287 return Q_ERR_SUCCESS;
302 size_t w, h, offset, size, endpos;
304 if (rawlen <
sizeof(miptex_t)) {
305 return Q_ERR_FILE_TOO_SMALL;
308 mt = (miptex_t *)rawdata;
310 w = LittleLong(mt->width);
311 h = LittleLong(mt->height);
312 if (w < 1 || h < 1 || w > 512 || h > 512) {
313 return Q_ERR_INVALID_FORMAT;
318 offset = LittleLong(mt->offsets[0]);
319 endpos = offset + size;
320 if (endpos < offset || endpos > rawlen) {
321 return Q_ERR_BAD_EXTENT;
324 *pic = IMG_AllocPixels(size * 4);
326 image->upload_width = image->width = w;
327 image->upload_height = image->height = h;
328 image->flags |=
IMG_Unpack8((uint32_t *)*pic, (uint8_t *)mt + offset, w, h);
330 return Q_ERR_SUCCESS;
344 byte* data = stbi_load_from_memory(rawdata, rawlen, &w, &h, &
channels, 4);
347 return Q_ERR_LIBRARY_ERROR;
351 image->upload_width = image->width = w;
352 image->upload_height = image->height = h;
355 image->flags |= IF_OPAQUE;
357 return Q_ERR_SUCCESS;
371 stbi_flip_vertically_on_write(1);
375 return Q_ERR_SUCCESS;
377 return Q_ERR_LIBRARY_ERROR;
382 stbi_flip_vertically_on_write(1);
386 return Q_ERR_SUCCESS;
388 return Q_ERR_LIBRARY_ERROR;
394 stbi_flip_vertically_on_write(1);
398 return Q_ERR_SUCCESS;
400 return Q_ERR_LIBRARY_ERROR;
416 const char *name,
const char *
ext)
425 "screenshots/", name,
ext);
429 for (i = 0; i < 1000; i++) {
431 ret =
FS_FOpenFile(buffer, &f, FS_MODE_WRITE | FS_FLAG_EXCL);
435 if (ret != Q_ERR_EXIST) {
436 Com_EPrintf(
"Couldn't exclusively open %s for writing: %s\n",
442 Com_EPrintf(
"All screenshot slots are full.\n");
447 qerror_t (*save)(qhandle_t,
const char *,
byte *,
int,
int,
int,
int),
450 char buffer[MAX_OSPATH];
462 ret = save(f, buffer, pixels, w, h, rowbytes, param);
463 FS_FreeTempMem(pixels);
468 Com_EPrintf(
"Couldn't write %s: %s\n", buffer,
Q_ErrorString(ret));
470 Com_Printf(
"Wrote %s\n", buffer);
489 Com_Printf(
"Usage: %s [format]\n",
Cmd_Argv(0));
526 Com_Printf(
"Usage: %s [name]\n",
Cmd_Argv(0));
538 Com_Printf(
"Usage: %s [name] [quality]\n",
Cmd_Argv(0));
556 Com_Printf(
"Usage: %s [name] [compression]\n",
Cmd_Argv(0));
578 byte *out,
int outwidth,
int outheight)
581 const byte *inrow1, *inrow2;
582 unsigned frac, fracstep;
583 unsigned p1[MAX_TEXTURE_SIZE], p2[MAX_TEXTURE_SIZE];
584 const byte *pix1, *pix2, *pix3, *pix4;
587 if (outwidth > MAX_TEXTURE_SIZE) {
588 Com_Error(ERR_FATAL,
"%s: outwidth > %d", __func__, MAX_TEXTURE_SIZE);
591 fracstep = inwidth * 0x10000 / outwidth;
593 frac = fracstep >> 2;
594 for (i = 0; i < outwidth; i++) {
595 p1[i] = 4 * (frac >> 16);
598 frac = 3 * (fracstep >> 2);
599 for (i = 0; i < outwidth; i++) {
600 p2[i] = 4 * (frac >> 16);
604 heightScale = (float)inheight / outheight;
606 for (i = 0; i < outheight; i++) {
607 inrow1 = in + inwidth * (
int)((i + 0.25f) * heightScale);
608 inrow2 = in + inwidth * (
int)((i + 0.75f) * heightScale);
609 for (j = 0; j < outwidth; j++) {
610 pix1 = inrow1 + p1[j];
611 pix2 = inrow1 + p2[j];
612 pix3 = inrow2 + p1[j];
613 pix4 = inrow2 + p2[j];
614 out[0] = (pix1[0] + pix2[0] + pix3[0] + pix4[0]) >> 2;
615 out[1] = (pix1[1] + pix2[1] + pix3[1] + pix4[1]) >> 2;
616 out[2] = (pix1[2] + pix2[2] + pix3[2] + pix4[2]) >> 2;
617 out[3] = (pix1[3] + pix2[3] + pix3[3] + pix4[3]) >> 2;
630 for (j = 0; j <
width; j += 8, out += 4, in += 8) {
631 out[0] = (in[0] + in[4] + in[
width + 0] + in[
width + 4]) >> 2;
632 out[1] = (in[1] + in[5] + in[
width + 1] + in[
width + 5]) >> 2;
633 out[2] = (in[2] + in[6] + in[
width + 2] + in[
width + 6]) >> 2;
634 out[3] = (in[3] + in[7] + in[
width + 3] + in[
width + 7]) >> 2;
647 #define RIMAGES_HASH 256
656 static const struct {
660 {
"pcx", IMG_LoadPCX },
661 {
"wal", IMG_LoadWAL },
662 {
"tga", IMG_LoadSTB },
663 {
"jpg", IMG_LoadSTB },
664 {
"png", IMG_LoadSTB }
687 char path[MAX_OSPATH];
691 Com_EPrintf(
"Error opening '%s'\n", path);
697 if (!image->registration_sequence)
701 sprintf(fmt,
"%%-%ds, %%-%ds, (%% 5d %% 5d), sRGB:%%d\n", MAX_QPATH, MAX_QPATH);
712 Com_Printf(
"Saved '%s'\n", path);
717 static const char types[8] =
"PFMSWY??";
719 Com_Printf(
"------------------\n");
723 if (!image->registration_sequence)
726 Com_Printf(
"%c%c%c%c %4i %4i %s: %s\n",
727 types[image->type > IT_MAX ? IT_MAX : image->type],
728 (image->flags & IF_TRANSPARENT) ?
'T' :
' ',
729 (image->flags & IF_SCRAP) ?
'S' :
' ',
730 (image->flags & IF_PERMANENT) ?
'*' :
' ',
732 image->upload_height,
733 (image->flags & IF_PALETTED) ?
"PAL" :
"RGB",
736 texels += image->upload_width * image->upload_height;
739 Com_Printf(
"Total images: %d (out of %d slots)\n", count,
r_numImages);
740 Com_Printf(
"Total texels: %d (not counting mipmaps)\n", texels);
751 if (!image->registration_sequence)
767 imagetype_t type,
unsigned hash,
size_t baselen)
773 if (image->type != type) {
776 if (image->baselen != baselen) {
779 if (!FS_pathcmpn(image->name, name, baselen)) {
794 len = FS_LoadFile(image->name, (
void **)&data);
800 ret =
img_loaders[fmt].load(data, len, image, pic);
804 image->filepath[0] = 0;
806 strcpy(image->filepath, image->name);
808 image->last_modified = 0;
811 return ret < 0 ? ret : fmt;
817 memcpy(image->name + image->baselen + 1,
img_loaders[fmt].ext, 4);
837 if (ret != Q_ERR_NOENT) {
843 fmt = (image->type == IT_WALL) ? IM_WAL : IM_PCX;
853 char buffer[MAX_QPATH];
860 memcpy(buffer, image->name, image->baselen + 1);
864 memcpy(buffer + image->baselen + 1,
"wal", 4);
867 len =
FS_Read(&mt,
sizeof(mt), f);
868 if (len ==
sizeof(mt)) {
869 w = LittleLong(mt.width);
870 h = LittleLong(mt.height);
875 memcpy(buffer + image->baselen + 1,
"pcx", 4);
878 len =
FS_Read(&pcx,
sizeof(pcx), f);
879 if (len ==
sizeof(pcx)) {
880 w = LittleShort(pcx.xmax) + 1;
881 h = LittleShort(pcx.ymax) + 1;
887 if (w < 1 || h < 1 || w > 512 || h > 512) {
904 for (s = self->string; *s; s++) {
906 case 't':
case 'T': i = IM_TGA;
break;
907 case 'j':
case 'J': i = IM_JPG;
break;
908 case 'p':
case 'P': i = IM_PNG;
break;
933 size_t len = strlen(name);
937 return Q_ERR_NAMETOOSHORT;
939 if (name[len - 4] !=
'.') {
940 return Q_ERR_INVALID_PATH;
943 memcpy(image->name, name, len + 1);
944 image->baselen = len - 4;
947 image->registration_sequence = 1;
950 for (fmt = 0; fmt < IM_MAX; fmt++) {
951 if (!Q_stricmp(image->name + image->baselen + 1,
img_loaders[fmt].ext)) {
961 if (ret == Q_ERR_NOENT) {
968 if (fmt <= IM_WAL && ret > IM_WAL) {
974 if (fmt <= IM_WAL && ret > IM_WAL) {
979 memset(image, 0,
sizeof(*image));
983 #if USE_REF == REF_VKPT
984 image->pix_data = pic;
987 return Q_ERR_SUCCESS;
992 imagetype_t type, imageflags_t flags,
1005 return Q_ERR_NAMETOOSHORT;
1007 if (name[len - 4] !=
'.') {
1008 return Q_ERR_INVALID_PATH;
1014 if ((image =
lookup_image(name, type, hash, len - 4)) != NULL) {
1015 image->flags |= flags & IF_PERMANENT;
1018 return Q_ERR_SUCCESS;
1024 return Q_ERR_OUT_OF_SLOTS;
1028 if (!
vid_rtx->integer && (type != IT_PIC))
1029 override_textures = 0;
1031 for (
int use_override = override_textures; use_override >= 0; use_override--)
1036 const char* last_slash = strrchr(name,
'/');
1042 strcpy(image->name,
"overrides/");
1043 strcat(image->name, last_slash);
1044 image->baselen = strlen(image->name) - 4;
1048 memcpy(image->name, name, len + 1);
1049 image->baselen = len - 4;
1052 image->flags = flags;
1056 for (fmt = 0; fmt < IM_MAX; fmt++) {
1057 if (!Q_stricmp(image->name + image->baselen + 1,
img_loaders[fmt].ext)) {
1065 if (fmt == IM_MAX) {
1068 if (ret == Q_ERR_NOENT) {
1070 ret = Q_ERR_INVALID_PATH;
1073 else if (override_textures) {
1080 if (ret == Q_ERR_NOENT) {
1087 image->last_modified = 0;
1092 memcpy(image->name, name, len + 1);
1093 image->baselen = len - 4;
1098 if (fmt <= IM_WAL && ret > IM_WAL) {
1107 memset(image, 0,
sizeof(*image));
1113 image->is_srgb = !!(flags & IF_SRGB);
1119 return Q_ERR_SUCCESS;
1129 Com_Error(ERR_FATAL,
"%s: NULL", __func__);
1134 if (len >= MAX_QPATH) {
1135 Com_Error(ERR_FATAL,
"%s: oversize name", __func__);
1144 if (ret != Q_ERR_NOENT) {
1145 Com_EPrintf(
"Couldn't load %s: %s\n", name,
Q_ErrorString(ret));
1159 Com_Error(ERR_FATAL,
"%s: %d out of range", __func__, h);
1171 imageflags_t flags, qerror_t *err_p)
1174 char fullname[MAX_QPATH];
1181 *err_p = Q_ERR_NAMETOOSHORT;
1188 *err_p = Q_ERR_AGAIN;
1192 if (type == IT_SKIN) {
1194 }
else if (*name ==
'/' || *name ==
'\\') {
1197 len =
Q_concat(fullname,
sizeof(fullname),
"pics/", name, NULL);
1198 if (len >=
sizeof(fullname)) {
1199 err = Q_ERR_NAMETOOLONG;
1206 if (len >=
sizeof(fullname)) {
1207 err = Q_ERR_NAMETOOLONG;
1214 *err_p = Q_ERR_SUCCESS;
1222 else if (
err != Q_ERR_NOENT)
1233 int len = strlen(name);
1237 if ((image =
lookup_image(name, type, hash, len)) != NULL) {
1238 image->flags |= flags & IF_PERMANENT;
1249 memcpy(image->name, name, len + 1);
1250 image->baselen = len;
1252 image->flags = flags;
1254 image->last_modified = 0;
1255 image->width =
width;
1257 image->upload_width =
width;
1258 image->upload_height =
height;
1262 image->is_srgb = !!(flags & IF_SRGB);
1277 if (image->registration_sequence)
1279 image->registration_sequence = -1;
1299 return !!(image->flags & IF_TRANSPARENT);
1317 #if USE_REF == REF_SOFT
1319 Com_PageInMemory(image->pixels[0], image->upload_width * image->upload_height * 4);
1323 if (!image->registration_sequence)
1325 if (image->flags & (IF_PERMANENT | IF_SCRAP))
1329 List_Remove(&image->entry);
1334 memset(image, 0,
sizeof(*image));
1339 Com_DPrintf(
"%s: %i images freed\n", __func__, count);
1349 if (!image->registration_sequence)
1354 memset(image, 0,
sizeof(*image));
1359 Com_DPrintf(
"%s: %i images freed\n", __func__, count);
1378 byte pal[768], *src, *data;
1398 for (i = 0, src = pal; i < 255; i++, src += 3) {
1399 d_8to24table[i] = MakeColor(src[0], src[1], src[2], 255);
1403 d_8to24table[i] = MakeColor(src[0], src[1], src[2], 0);