/* * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or * its licensors. * * For complete copyright and license terms please see the LICENSE at the root of this * distribution (the "License"). All use of this software is governed by the License, * or, if provided, by the license below or the license accompanying this file. Do not * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * */ // Original file Copyright Crytek GMBH or its affiliates, used under license. // Description : Image utilities implementation. #include "StdAfx.h" #include "ImageUtil.h" #include "ImageGif.h" #include "ImageTIF.h" #include "ImageHDR.h" #include "Image_DXTC.h" #include "ImageASC.h" #include "ImageBT.h" #include "CryFile.h" #include "GdiUtil.h" ////////////////////////////////////////////////////////////////////////// bool CImageUtil::Save(const QString& strFileName, CImageEx& inImage) { QImage imgBitmap; ImageToQImage(inImage, imgBitmap); // Explicitly set the pixels per meter in our images to a consistent default. // The normal default is 96 pixels per inch, or 3780 pixels per meter. // However, the Windows scaling display setting can cause these numbers to vary // on different machines, producing output files that have slightly different // headers from machine to machine, which often isn't desirable. const int defaultPixelsPerMeter = 3780; imgBitmap.setDotsPerMeterX(defaultPixelsPerMeter); imgBitmap.setDotsPerMeterY(defaultPixelsPerMeter); return imgBitmap.save(strFileName); } ////////////////////////////////////////////////////////////////////////// bool CImageUtil::SaveBitmap(const QString& szFileName, CImageEx& inImage) { return Save(szFileName, inImage); } ////////////////////////////////////////////////////////////////////////// bool CImageUtil::SaveJPEG(const QString& strFileName, CImageEx& inImage) { return Save(strFileName, inImage); } ////////////////////////////////////////////////////////////////////////// bool CImageUtil::Load(const QString& fileName, CImageEx& image) { QImage imgBitmap(fileName); if (imgBitmap.isNull()) { CLogFile::FormatLine("Invalid file: %s", fileName.toUtf8().data()); return false; } return QImageToImage(imgBitmap, image); } ////////////////////////////////////////////////////////////////////////// bool CImageUtil::LoadJPEG(const QString& strFileName, CImageEx& outImage) { return CImageUtil::Load(strFileName, outImage); } //=========================================================================== bool CImageUtil::LoadBmp(const QString& fileName, CImageEx& image) { return CImageUtil::Load(fileName, image); } ////////////////////////////////////////////////////////////////////////// bool CImageUtil::SavePGM(const QString& fileName, const CImageEx& image) { // There are two types of PGM ("Portable Grey Map") files - "raw" (binary) and "plain" (ASCII). This function supports the "plain PGM" format. // See http://netpbm.sourceforge.net/doc/pgm.html or https://en.wikipedia.org/wiki/Netpbm_format for the definition. uint32 width = image.GetWidth(); uint32 height = image.GetHeight(); uint32* pixels = image.GetData(); // Create the file header. string fileHeader; fileHeader.Format( // P2 = PGM header for ASCII output. (P5 is PGM header for binary output) "P2\n" // width and height of the image "%d %d\n" // The maximum grey value in the file. (i.e. the max value for any given pixel below) "65535\n" , width, height); FILE* file = nullptr; azfopen(&file, fileName.toUtf8().data(), "wt"); if (!file) { return false; } // First print the file header fprintf(file, fileHeader.c_str()); // Then print all the pixels. for (int32 y = 0; y < height; y++) { for (int32 x = 0; x < width; x++) { fprintf(file, "%d ", pixels[x + (y * width)]); } fprintf(file, "\n"); } fclose(file); return true; } ////////////////////////////////////////////////////////////////////////// bool CImageUtil::LoadPGM(const QString& fileName, CImageEx& image) { FILE* file = nullptr; azfopen(&file, fileName.toUtf8().data(), "rt"); if (!file) { return false; } const char seps[] = " \n\t\r"; char* token; int32 width = 0; int32 height = 0; int32 numColors = 1; fseek(file, 0, SEEK_END); int fileSize = ftell(file); fseek(file, 0, SEEK_SET); char* str = new char[fileSize]; fread(str, fileSize, 1, file); char* nextToken = nullptr; token = azstrtok(str, 0, seps, &nextToken); while (token != NULL && token[0] == '#') { if (token != NULL && token[0] == '#') { azstrtok(NULL, 0, "\n", &nextToken); } token = azstrtok(NULL, 0, seps, &nextToken); } if (azstricmp(token, "P2") != 0) { // Bad file. not supported pgm. delete[]str; fclose(file); return false; } do { token = azstrtok(NULL, 0, seps, &nextToken); if (token != NULL && token[0] == '#') { azstrtok(NULL, 0, "\n", &nextToken); } } while (token != NULL && token[0] == '#'); width = atoi(token); do { token = azstrtok(NULL, 0, seps, &nextToken); if (token != NULL && token[0] == '#') { azstrtok(NULL, 0, "\n", &nextToken); } } while (token != NULL && token[0] == '#'); height = atoi(token); do { token = azstrtok(NULL, 0, seps, &nextToken); if (token != NULL && token[0] == '#') { azstrtok(NULL, 0, "\n", &nextToken); } } while (token != NULL && token[0] == '#'); numColors = atoi(token); image.Allocate(width, height); uint32* p = image.GetData(); int size = width * height; int i = 0; while (token != NULL && i < size) { do { token = azstrtok(NULL, 0, seps, &nextToken); } while (token != NULL && token[0] == '#'); *p++ = atoi(token); i++; } delete[]str; fclose(file); // If we have 16-bit greyscale values that we're storing into 32-bit pixels, denote it with an appropriate texture type. if (numColors > 255) { image.SetFormat(eTF_R16G16); } return true; } ////////////////////////////////////////////////////////////////////////// bool CImageUtil::LoadImage(const QString& fileName, CImageEx& image, bool* pQualityLoss) { char drive[_MAX_DRIVE]; char dir[_MAX_DIR]; char fname[_MAX_FNAME]; char ext[_MAX_EXT]; if (pQualityLoss) { *pQualityLoss = false; } _splitpath(fileName.toUtf8().data(), drive, dir, fname, ext); // Only DDS has explicit sRGB flag - we'll assume by default all formats are stored in gamma space image.SetSRGB(true); if (azstricmp(ext, ".bmp") == 0) { return LoadBmp(fileName, image); } else if (azstricmp(ext, ".tif") == 0) { return CImageTIF().Load(fileName, image); } else if (azstricmp(ext, ".jpg") == 0) { if (pQualityLoss) { *pQualityLoss = true; // we assume JPG has quality loss } return LoadJPEG(fileName, image); } else if (azstricmp(ext, ".gif") == 0) { return CImageGif().Load(fileName, image); } else if (azstricmp(ext, ".pgm") == 0) { return LoadPGM(fileName, image); } else if (azstricmp(ext, ".dds") == 0) { return CImage_DXTC().Load(fileName.toUtf8().data(), image, pQualityLoss); } else if (azstricmp(ext, ".png") == 0) { return CImageUtil::Load(fileName, image); } else if (azstricmp(ext, ".hdr") == 0) { return CImageHDR().Load(fileName, image); } else { return CImageUtil::Load(fileName, image); } return false; } ////////////////////////////////////////////////////////////////////////// bool CImageUtil::SaveImage(const QString& fileName, CImageEx& image) { char drive[_MAX_DRIVE]; char dir[_MAX_DIR]; char fname[_MAX_FNAME]; char ext[_MAX_EXT]; // Remove the read-only attribute so the file can be overwritten. QFile(fileName).setPermissions(QFile::ReadUser | QFile::WriteUser); _splitpath(fileName.toUtf8().data(), drive, dir, fname, ext); if (azstricmp(ext, ".bmp") == 0) { return SaveBitmap(fileName, image); } else if (azstricmp(ext, ".jpg") == 0) { return SaveJPEG(fileName, image); } else if (azstricmp(ext, ".pgm") == 0) { return SavePGM(fileName, image); } else { return Save(fileName, image); } return false; } ////////////////////////////////////////////////////////////////////////// void CImageUtil::ScaleToFit(const CByteImage& srcImage, CByteImage& trgImage) { trgImage.ScaleToFit(srcImage); } ////////////////////////////////////////////////////////////////////////// void CImageUtil::DownScaleSquareTextureTwice(const CImageEx& srcImage, CImageEx& trgImage, IImageUtil::_EAddrMode eAddressingMode) { uint32* pSrcData = srcImage.GetData(); int nSrcWidth = srcImage.GetWidth(); int nSrcHeight = srcImage.GetHeight(); int nTrgWidth = srcImage.GetWidth() >> 1; int nTrgHeight = srcImage.GetHeight() >> 1; // reallocate target trgImage.Release(); trgImage.Allocate(nTrgWidth, nTrgHeight); uint32* pDstData = trgImage.GetData(); // values in this filter are the log2 of the actual multiplicative values .. see DXCFILTER_BLUR3X3 for the used 3x3 filter static int filter[3][3] = { {0, 1, 0}, {1, 2, 1}, {0, 1, 0} }; for (int i = 0; i < nTrgHeight; i++) { for (int j = 0; j < nTrgWidth; j++) { // filter3x3 int x = j << 1; int y = i << 1; int r, g, b, a; r = b = g = a = 0; uint32 col; if (eAddressingMode == IImageUtil::WRAP) // TODO: this condition could be compile-time static by making it a template arg { for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { col = pSrcData[((y + nSrcHeight + i - 1) % nSrcHeight) * nSrcWidth + ((x + nSrcWidth + j - 1) % nSrcWidth)]; r += (col & 0xff) << filter[i][j]; g += ((col >> 8) & 0xff) << filter[i][j]; b += ((col >> 16) & 0xff) << filter[i][j]; a += ((col >> 24) & 0xff) << filter[i][j]; } } } else { assert(eAddressingMode == IImageUtil::CLAMP); for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { int x1 = clamp_tpl((x + j), 0, nSrcWidth - 1); int y1 = clamp_tpl((y + i), 0, nSrcHeight - 1); col = pSrcData[ y1 * nSrcWidth + x1]; r += (col & 0xff) << filter[i][j]; g += ((col >> 8) & 0xff) << filter[i][j]; b += ((col >> 16) & 0xff) << filter[i][j]; a += ((col >> 24) & 0xff) << filter[i][j]; } } } // the sum of the multiplicative values here is 16 so we shift by 4 bits r >>= 4; g >>= 4; b >>= 4; a >>= 4; uint32 res = r + (g << 8) + (b << 16) + (a << 24); *pDstData++ = res; } } } ////////////////////////////////////////////////////////////////////////// void CImageUtil::ScaleToFit(const CImageEx& srcImage, CImageEx& trgImage) { trgImage.ScaleToFit(srcImage); } ////////////////////////////////////////////////////////////////////////// void CImageUtil::ScaleToDoubleFit(const CImageEx& srcImage, CImageEx& trgImage) { uint32 x, y, u, v; unsigned int* destRow, * dest, * src, * sourceRow; uint32 srcW = srcImage.GetWidth(); uint32 srcH = srcImage.GetHeight(); uint32 trgHalfW = trgImage.GetWidth() / 2; uint32 trgH = trgImage.GetHeight(); uint32 xratio = trgHalfW > 0 ? (srcW << 16) / trgHalfW : 1; uint32 yratio = trgH > 0 ? (srcH << 16) / trgH : 1; src = srcImage.GetData(); destRow = trgImage.GetData(); v = 0; for (y = 0; y < trgH; y++) { u = 0; sourceRow = src + (v >> 16) * srcW; dest = destRow; for (x = 0; x < trgHalfW; x++) { *(dest + trgHalfW) = sourceRow[u >> 16]; *dest++ = sourceRow[u >> 16]; u += xratio; } v += yratio; destRow += trgHalfW * 2; } } ////////////////////////////////////////////////////////////////////////// void CImageUtil::SmoothImage(CByteImage& image, int numSteps) { assert(numSteps > 0); uint8* buf = image.GetData(); int w = image.GetWidth(); int h = image.GetHeight(); for (int steps = 0; steps < numSteps; steps++) { // Smooth the image. for (int y = 1; y < h - 1; y++) { // Precalculate for better speed uint8* ptr = &buf[y * w + 1]; for (int x = 1; x < w - 1; x++) { // Smooth it out *ptr = ( (uint32)ptr[1] + ptr[w] + ptr[-1] + ptr[-w] + ptr[w + 1] + ptr[w - 1] + ptr[-w + 1] + ptr[-w - 1] ) >> 3; // Next pixel ptr++; } } } } unsigned char CImageUtil::GetBilinearFilteredAt(const int iniX256, const int iniY256, const CByteImage& image) { // assert(image.IsValid()); if(!image.IsValid())return(0); // this shouldn't be DWORD x = (DWORD)(iniX256) >> 8; DWORD y = (DWORD)(iniY256) >> 8; if (x >= image.GetWidth() - 1 || y >= image.GetHeight() - 1) { return image.ValueAt(x, y); // border is not filtered, 255 to get in range 0..1 } DWORD rx = (DWORD)(iniX256) & 0xff; // fractional aprt DWORD ry = (DWORD)(iniY256) & 0xff; // fractional aprt DWORD top = (DWORD)image.ValueAt((int)x, (int)y) * (256 - rx) // left top + (DWORD)image.ValueAt((int)x + 1, (int)y) * rx; // right top DWORD bottom = (DWORD)image.ValueAt((int)x, (int)y + 1) * (256 - rx) // left bottom + (DWORD)image.ValueAt((int)x + 1, (int)y + 1) * rx; // right bottom return (unsigned char)((top * (256 - ry) + bottom * ry) >> 16); } bool CImageUtil::QImageToImage(const QImage& bitmap, CImageEx& image) { QImage convertedBitmap; const QImage *srcBitmap = &bitmap; if (bitmap.format() != QImage::Format_RGBA8888) { convertedBitmap = bitmap.convertToFormat(QImage::Format_RGBA8888); srcBitmap = &convertedBitmap; } if (image.Allocate(srcBitmap->width(), srcBitmap->height()) == false) { return false; } AZStd::copy(srcBitmap->bits(), srcBitmap->bits() + (srcBitmap->width() * srcBitmap->height() * sizeof(uint32)), reinterpret_cast(image.GetData())); return true; } bool CImageUtil::ImageToQImage(const CImageEx& image, QImage& bitmapObj) { bitmapObj = QImage(image.GetWidth(), image.GetHeight(), QImage::Format_RGBA8888); AZStd::copy(image.GetData(), image.GetData() + image.GetWidth() * image.GetHeight(), reinterpret_cast(bitmapObj.bits())); return true; }