#include #include #include #include #include #ifdef _WIN32 #include #else #include #define MAX_PATH PATH_MAX #endif #include #include "tim.h" #define VERSION "0.75" namespace param { char InputFile[MAX_PATH] = { 0 }; char OutputFile[MAX_PATH] = { 0 }; int BlackTrans = false; // Make black pixels transparent (STP bit off) int ColorTrans = false; // Make non-black pixels translucent (STP bit on) int UseAlphaMask = false; // Use alpha channel as transparency mask int AlphaThresh = 127; // Alpha mask threshold int TransColR = -1; int TransColG = -1; int TransColB = -1; int TransCol = -1; int OutputBpp = -1; tim::PARAM tim = { 0 }; } typedef struct { int fmt; // 0 - 32-bit RGBA, 1 - 8-bit palletized, 2 - 4-bit palletized short w,h; void *pixels; short numCols; void *colors; } IMGPARAM; void ConvertImageToTim(IMGPARAM image, tim::PARAM* tim); int SimpleQuantize(tim::PARAM* tim, int bitDepth); int LoadImagePixels(const char* fileName, IMGPARAM* image, bool makeRGBA); void FreeImageStruct(IMGPARAM* image); int main(int argc, char *argv[]) { FreeImage_Initialise(false); printf("img2tim v%s - Converts most image files into PlayStation TIM files\n", VERSION); printf("2016 Meido-Tek Productions (Lameguy64/TheCodingBrony)\n"); printf("Powered by FreeImage v%s\n\n", FreeImage_GetVersion()); // Print banner if no arguments were passed if (argc <= 1) { printf("Syntax:\n"); printf(" img2tim [options] [-o ] \n\n"); printf("Options:\n"); printf(" -b - Sets semi-transparent bit on fully black pixels\n"); printf(" (ignored when -usealpha is specified).\n"); printf(" -t - Set semi-transparent bit on non fully black pixels.\n"); printf(" -org - Specifies the VRAM offset of the image (Default: 640 0).\n"); printf(" -plt - Specifies the VRAM offset of the CLUT (Default: 0 480).\n"); printf(" -o - Sets the name of the output file (Default: .tim).\n"); printf("\nExtra options:\n"); printf(" -usealpha - Use alpha channel (if available) as transparency mask.\n"); printf(" -alpt - Threshold value when alpha channel is used as transparency\n"); printf(" mask (Default: 127).\n"); printf(" -tindex - Specify color index to be treated as transparent (ignored on\n"); printf(" non palletized images).\n"); printf(" -tcol - Specify RGB color value to be treated as transparent.\n"); printf(" -bpp - Specify color depth for the output TIM file\n"); printf(" (Default: Color depth of source image, 24 is never default).\n"); /* printf(" -tc - Specify color to be treated as transparent.\n"); printf(" -bc - Specify blending color when converting alpha-blended pixels.\n"); printf(" -bg - Specifies a background color for images with an alpha channel.\n"); printf(" -tc - Sepecifies the color to be treated as transparent.\n"); printf(" -qm - Sets the quantization method used for generating a CLUT when\n"); printf(" converting true-color images to 4-bit/8-bit.\n"); printf(" 0 - Simple 'color search' quantization (default)\n"); printf(" 1 - Median-cut quantization\n"); printf(" 2 - Median-cut quantization with dithering\n"); */ printf("\nFor more info, please read img2tim.txt.\n"); return(0); } param::tim.imgXoffs = 640; param::tim.imgYoffs = 0; param::tim.clutXoffs = 0; param::tim.clutYoffs = 480; // Parse arguments for(int i=1; iimgData = malloc(3*(image.w*image.h)); for(short py=0; pyimgData)[px+(image.w*py)].r = r; ((tim::PIX_RGB24*)tim->imgData)[px+(image.w*py)].g = g; ((tim::PIX_RGB24*)tim->imgData)[px+(image.w*py)].b = b; } } tim->format = 3; tim->imgWidth = image.w; tim->imgHeight = image.h; } else { // Convert image from 32-bit RGBA to 16-bit RGBi tim->imgData = malloc(2*(image.w*image.h)); for(short py=0; pyimgData)[px+(image.w*py)].r = r/8; ((tim::PIX_RGB5*)tim->imgData)[px+(image.w*py)].g = g/8; ((tim::PIX_RGB5*)tim->imgData)[px+(image.w*py)].b = b/8; if (param::UseAlphaMask == false) { // For fully-black pixels if ((r == 0) && (g == 0) && (b == 0)) { if (param::BlackTrans) ((tim::PIX_RGB5*)tim->imgData)[px+(image.w*py)].i = 1; else ((tim::PIX_RGB5*)tim->imgData)[px+(image.w*py)].i = 0; // For non-fully black pixels } else { if (param::ColorTrans) ((tim::PIX_RGB5*)tim->imgData)[px+(image.w*py)].i = 1; else ((tim::PIX_RGB5*)tim->imgData)[px+(image.w*py)].i = 0; } } else { if ((r == 0) && (g == 0) && (b == 0)) { if (a >= param::AlphaThresh) ((tim::PIX_RGB5*)tim->imgData)[px+(image.w*py)].i = 1; else ((tim::PIX_RGB5*)tim->imgData)[px+(image.w*py)].i = 0; } else { if (a < param::AlphaThresh) { ((tim::PIX_RGB5*)tim->imgData)[px+(image.w*py)].r = 0; ((tim::PIX_RGB5*)tim->imgData)[px+(image.w*py)].g = 0; ((tim::PIX_RGB5*)tim->imgData)[px+(image.w*py)].b = 0; ((tim::PIX_RGB5*)tim->imgData)[px+(image.w*py)].i = 0; } else { if (param::ColorTrans) ((tim::PIX_RGB5*)tim->imgData)[px+(image.w*py)].i = 1; else ((tim::PIX_RGB5*)tim->imgData)[px+(image.w*py)].i = 0; } } } if ((param::TransColR == r) && (param::TransColG == g) && (param::TransColB == b)) { ((tim::PIX_RGB5*)tim->imgData)[px+(image.w*py)].r = 0; ((tim::PIX_RGB5*)tim->imgData)[px+(image.w*py)].g = 0; ((tim::PIX_RGB5*)tim->imgData)[px+(image.w*py)].b = 0; ((tim::PIX_RGB5*)tim->imgData)[px+(image.w*py)].i = 0; } } } tim->format = 2; tim->imgWidth = image.w; tim->imgHeight = image.h; } } else if (image.fmt == 1) { // Convert 8-bit palletized if ((image.w%2) != 0) printf("WARNING: Image width is not a multiple of 2, output may be broken.\n"); tim->clutData = malloc(2*256); tim->clutWidth = 256; tim->clutHeight = 1; for(short c=0; c<256; c++) { u_char r = ((RGBQUAD*)image.colors)[c].rgbRed; u_char g = ((RGBQUAD*)image.colors)[c].rgbGreen; u_char b = ((RGBQUAD*)image.colors)[c].rgbBlue; if (param::TransCol == -1) { if ((param::TransColR == r) && (param::TransColG == g) && (param::TransColB == b)) { param::TransCol = c; } } if (c != param::TransCol) { ((tim::PIX_RGB5*)tim->clutData)[c].r = r/8; ((tim::PIX_RGB5*)tim->clutData)[c].g = g/8; ((tim::PIX_RGB5*)tim->clutData)[c].b = b/8; // For fully-black pixels if ((r == 0) && (g == 0) && (b == 0)) { if (param::BlackTrans) ((tim::PIX_RGB5*)tim->clutData)[c].i = 1; else ((tim::PIX_RGB5*)tim->clutData)[c].i = 0; // For non-fully black pixels } else { if (param::ColorTrans) ((tim::PIX_RGB5*)tim->clutData)[c].i = 1; else ((tim::PIX_RGB5*)tim->clutData)[c].i = 0; } } else { ((tim::PIX_RGB5*)tim->clutData)[c].r = 0; ((tim::PIX_RGB5*)tim->clutData)[c].g = 0; ((tim::PIX_RGB5*)tim->clutData)[c].b = 0; ((tim::PIX_RGB5*)tim->clutData)[c].i = 0; } } tim->imgData = malloc(image.w*image.h); for(short py=0; pyimgData)[image.w*py], &((u_char*)image.pixels)[image.w*py], image.w); } tim->format = 1; tim->imgWidth = image.w; tim->imgHeight = image.h; } else if (image.fmt == 2) { // Convert image for 4-bit TIM if ((image.w%4) != 0) printf("WARNING: Image width is not a multiple of 4, output may be broken.\n"); tim->clutData = malloc(2*16); tim->clutWidth = 16; tim->clutHeight = 1; for(short c=0; c<16; c++) { u_char r = ((RGBQUAD*)image.colors)[c].rgbRed; u_char g = ((RGBQUAD*)image.colors)[c].rgbGreen; u_char b = ((RGBQUAD*)image.colors)[c].rgbBlue; if (param::TransCol == -1) { if ((param::TransColR == r) && (param::TransColG == g) && (param::TransColB == b)) { param::TransCol = c; } } if (c != param::TransCol) { ((tim::PIX_RGB5*)tim->clutData)[c].r = r/8; ((tim::PIX_RGB5*)tim->clutData)[c].g = g/8; ((tim::PIX_RGB5*)tim->clutData)[c].b = b/8; // For fully-black pixels if ((r == 0) && (g == 0) && (b == 0)) { if (param::BlackTrans) ((tim::PIX_RGB5*)tim->clutData)[c].i = 1; else ((tim::PIX_RGB5*)tim->clutData)[c].i = 0; // For non-fully black pixels } else { if (param::ColorTrans) ((tim::PIX_RGB5*)tim->clutData)[c].i = 1; else ((tim::PIX_RGB5*)tim->clutData)[c].i = 0; } } else { ((tim::PIX_RGB5*)tim->clutData)[c].r = 0; ((tim::PIX_RGB5*)tim->clutData)[c].g = 0; ((tim::PIX_RGB5*)tim->clutData)[c].b = 0; ((tim::PIX_RGB5*)tim->clutData)[c].i = 0; } } tim->imgData = malloc((image.w/2)*image.h); for(short py=0; pyimgData)[px+((image.w/2)*py)] = ((pix&0xf)<<4)|((pix>>4)&0xf); } } tim->format = 0; tim->imgWidth = image.w; tim->imgHeight = image.h; } } int SimpleQuantize(tim::PARAM* tim, int bitDepth) { u_short colTable[256] = { 0 }; int diffColors = 0; bool newCol = true; if (bitDepth == 8) { if ((tim->imgWidth%2) != 0) printf("WARNING: Image width is not a multiple of 2, output may be broken.\n"); } else { if ((tim->imgWidth%4) != 0) printf("WARNING: Image width is not a multiple of 4, output may be broken.\n"); } for(int py=0; pyimgHeight; py++) { for(int px=0; pximgWidth; px++) { u_short pix = ((u_short*)tim->imgData)[px+(tim->imgWidth*py)]; for(short c=0; c= 255) { printf("ERROR: More than 256 colors found in image.\n"); return(0); } } else { if (diffColors >= 16) { printf("ERROR: More than 16 colors found in image.\n"); return(0); } } colTable[diffColors] = pix; diffColors++; newCol = false; } } } u_short* srcImage = (u_short*)tim->imgData; if (bitDepth == 8) { tim->imgData = malloc(tim->imgWidth*tim->imgHeight); for(int py=0; pyimgHeight; py++) { for(int px=0; pximgWidth; px++) { u_short pix = srcImage[px+(tim->imgWidth*py)]; short index; for(index=0; indeximgData)[px+(tim->imgWidth*py)] = index; } } } } else { tim->imgData = malloc((tim->imgWidth/2)*tim->imgHeight); for(int py=0; pyimgHeight; py++) { for(int px=0; pximgWidth; px++) { u_short pix = srcImage[px+(tim->imgWidth*py)]; short index; for(index=0; indeximgData)[(px/2)+((tim->imgWidth/2)*py)] = index; } else { ((u_char*)tim->imgData)[(px/2)+((tim->imgWidth/2)*py)] |= index<<4; } } } } } free(srcImage); if (bitDepth == 8) { tim->clutData = malloc(2*256); memcpy(tim->clutData, colTable, 2*256); tim->clutWidth = 256; } else { tim->clutData = malloc(2*16); memcpy(tim->clutData, colTable, 2*16); tim->clutWidth = 16; } tim->clutHeight = 1; if (bitDepth == 8) { tim->format = 1; } else { tim->format = 0; } return(1); } int LoadImagePixels(const char* fileName, IMGPARAM* image, bool makeRGBA) { // Check if file exists if (access(fileName, F_OK) == -1) { printf("ERROR: File not found.\n"); return(0); } // Determine format of input file FREE_IMAGE_FORMAT fif = FreeImage_GetFileType(fileName, 0); if (fif == FIF_UNKNOWN) { fif = FreeImage_GetFIFFromFilename(fileName); if (!FreeImage_FIFSupportsReading(fif)) { printf("ERROR: Unknown/unsupported image format.\n"); return(0); } } // Load the input image FIBITMAP *srcImage = FreeImage_Load(fif, fileName, 0); if (srcImage == NULL) { printf("ERROR: Cannot load specified image file.\n"); return(0); } // Some checks to make sure that the image is really valid if (!FreeImage_HasPixels(srcImage)) { printf("ERROR: Source image has no pixel data... Somehow.\n"); return(0); } if ((makeRGBA) || (FreeImage_GetBPP(srcImage) == 24) || ((FreeImage_GetBPP(srcImage) > 32))) { // Convert to RGBA32 for 16-bit and 24-bit exports FIBITMAP *tempImage; // Just to make things simpler, convert pixels to RGB32 when it is not palletized tempImage = FreeImage_ConvertTo32Bits(srcImage); if (tempImage == NULL) { printf("ERROR: Could not convert image to RGBA32 for conversion.\n"); FreeImage_Unload(srcImage); return(0); } FreeImage_Unload(srcImage); srcImage = tempImage; } switch(FreeImage_GetBPP(srcImage)) { case 32: // 32-bit images image->fmt = 0; image->w = FreeImage_GetWidth(srcImage); image->h = FreeImage_GetHeight(srcImage); image->numCols = 0; image->colors = NULL; image->pixels = malloc(4*(image->w*image->h)); for(short py=0; pyh; py++) { void* pixels = FreeImage_GetScanLine(srcImage, (image->h-py)-1); memcpy(&((u_int*)image->pixels)[image->w*py], pixels, 4*image->w); for(short p=0; pw; p++) { u_char* pix = (u_char*)&((u_int*)image->pixels)[ p+(image->w*py) ]; u_char t; t = pix[0]; pix[0] = pix[2]; pix[2] = t; } } break; case 8: // 8-bit palletized image->fmt = 1; image->w = FreeImage_GetWidth(srcImage); image->h = FreeImage_GetHeight(srcImage); image->numCols = 256; image->colors = malloc(4*256); memcpy(image->colors, FreeImage_GetPalette(srcImage), 4*256); image->pixels = malloc(image->w*image->h); for(short py=0; pyh; py++) { void* pixels = FreeImage_GetScanLine(srcImage, (image->h-py)-1); memcpy(&((u_char*)image->pixels)[image->w*py], pixels, image->w); } break; case 4: // 4-bit palletized image->fmt = 2; image->w = FreeImage_GetWidth(srcImage); image->h = FreeImage_GetHeight(srcImage); image->numCols = 16; image->colors = malloc(4*16); memcpy(image->colors, FreeImage_GetPalette(srcImage), 4*16); image->pixels = malloc((image->w/2)*image->h); for(short py=0; pyh; py++) { void* pixels = FreeImage_GetScanLine(srcImage, (image->h-py)-1); memcpy(&((u_char*)image->pixels)[(image->w/2)*py], pixels, image->w/2); } break; default: printf("ERROR: Unsupported color depth: %dbpp", FreeImage_GetBPP(srcImage)); FreeImage_Unload(srcImage); return(0); } // Unload source image FreeImage_Unload(srcImage); return(1); } void FreeImageStruct(IMGPARAM* image) { if (image->pixels != NULL) { free(image->pixels); image->pixels = NULL; } if (image->colors != NULL) { free(image->colors); image->colors = NULL; } }