/* * 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. * */ #include #include #include #include #include #include #include #include #include #include namespace ImageProcessing { //limited to 1 thread because AP requires so. We may change to n when AP allocate n thread to a job in the furture static const int MAX_COMP_JOBS = 1; static const int MIN_COMP_JOBS = 1; static const float ETC_LOW_EFFORT_LEVEL = 25.0f; static const float ETC_MED_EFFORT_LEVEL = 40.0f; static const float ETC_HIGH_EFFORT_LEVEL = 80.0f; //Grab the Etc2Comp specific pixel format enum static Etc::Image::Format FindEtc2PixelFormat(EPixelFormat fmt) { switch (fmt) { case ePixelFormat_EAC_RG11: return Etc::Image::Format::RG11; case ePixelFormat_EAC_R11: return Etc::Image::Format::R11; case ePixelFormat_ETC2: return Etc::Image::Format::RGB8; case ePixelFormat_ETC2a: return Etc::Image::Format::RGBA8; default: return Etc::Image::Format::FORMATS; } } //Get the errmetric required for the compression static Etc::ErrorMetric FindErrMetric(Etc::Image::Format fmt) { switch (fmt) { case Etc::Image::Format::RG11: return Etc::ErrorMetric::NORMALXYZ; case Etc::Image::Format::R11: return Etc::ErrorMetric::NUMERIC; case Etc::Image::Format::RGB8: return Etc::ErrorMetric::RGBX; case Etc::Image::Format::RGBA8: return Etc::ErrorMetric::RGBA; default: return Etc::ErrorMetric::ERROR_METRICS; } } //Convert to sRGB format static Etc::Image::Format FindGammaEtc2PixelFormat(Etc::Image::Format fmt) { switch (fmt) { case Etc::Image::Format::RGB8: return Etc::Image::Format::SRGB8; case Etc::Image::Format::RGBA8: return Etc::Image::Format::SRGBA8; case Etc::Image::Format::RGB8A1: return Etc::Image::Format::SRGB8A1; default: return Etc::Image::Format::FORMATS; } } bool ETC2Compressor::IsCompressedPixelFormatSupported(EPixelFormat fmt) { return (FindEtc2PixelFormat(fmt) != Etc::Image::Format::FORMATS); } bool ETC2Compressor::IsUncompressedPixelFormatSupported(EPixelFormat fmt) { //for uncompress format if (fmt == ePixelFormat_R8G8B8A8) { return true; } return false; } EPixelFormat ETC2Compressor::GetSuggestedUncompressedFormat(EPixelFormat compressedfmt, EPixelFormat uncompressedfmt) { return ePixelFormat_R8G8B8A8; } bool ETC2Compressor::DoesSupportDecompress(EPixelFormat fmtDst) { return false; } IImageObjectPtr ETC2Compressor::CompressImage(IImageObjectPtr srcImage, EPixelFormat fmtDst, const CompressOption *compressOption) { const size_t srcPixelSize = 4; //validate input EPixelFormat fmtSrc = srcImage->GetPixelFormat(); //src format need to be uncompressed and dst format need to compressed. if (!IsUncompressedPixelFormatSupported(fmtSrc) || !IsCompressedPixelFormatSupported(fmtDst)) { return nullptr; } IImageObjectPtr dstImage(srcImage->AllocateImage(fmtDst)); //determinate compression quality ICompressor::EQuality quality = ICompressor::eQuality_Normal; //get setting from compression option if (compressOption) { quality = compressOption->compressQuality; } float qualityEffort = 0.0f; switch (quality) { case eQuality_Preview: case eQuality_Fast: { qualityEffort = ETC_LOW_EFFORT_LEVEL; break; } case eQuality_Normal: { qualityEffort = ETC_MED_EFFORT_LEVEL; break; } default: { qualityEffort = ETC_HIGH_EFFORT_LEVEL; } } Etc::Image::Format dstEtc2Format = FindEtc2PixelFormat(fmtDst); if (srcImage->GetImageFlags() & EIF_SRGBRead) { dstEtc2Format = FindGammaEtc2PixelFormat(dstEtc2Format); } //use to read pixel data from src image IPixelOperationPtr pixelOp = CreatePixelOperation(fmtSrc); //get count of bytes per pixel for images AZ::u32 pixelBytes = CPixelFormats::GetInstance().GetPixelFormatInfo(fmtSrc)->bitsPerBlock / 8; const AZ::u32 mipCount = dstImage->GetMipCount(); for (AZ::u32 mip = 0; mip < mipCount; ++mip) { const AZ::u32 width = srcImage->GetWidth(mip); const AZ::u32 height = srcImage->GetHeight(mip); // Prepare source data AZ::u8* srcMem; AZ::u32 srcPitch; srcImage->GetImagePointer(mip, srcMem, srcPitch); const AZ::u32 pixelCount = srcImage->GetPixelCount(mip); Etc::ColorFloatRGBA* rgbaPixels = new Etc::ColorFloatRGBA[pixelCount]; Etc::ColorFloatRGBA* rgbaPixelPtr = rgbaPixels; float r, g, b, a; for (AZ::u32 pixelIdx = 0; pixelIdx < pixelCount; pixelIdx++, srcMem += pixelBytes, rgbaPixelPtr++) { pixelOp->GetRGBA(srcMem, r, g, b, a); rgbaPixelPtr->fA = a; rgbaPixelPtr->fR = r; rgbaPixelPtr->fG = g; rgbaPixelPtr->fB = b; } //Call into etc2Comp lib to compress. https://medium.com/@duhroach/building-a-blazing-fast-etc2-compressor-307f3e9aad99 Etc::ErrorMetric errMetric = FindErrMetric(dstEtc2Format); unsigned char* paucEncodingBits; unsigned int uiEncodingBitsBytes; unsigned int uiExtendedWidth; unsigned int uiExtendedHeight; int iEncodingTime_ms; Etc::Encode(reinterpret_cast(rgbaPixels), width, height, dstEtc2Format, errMetric, qualityEffort, MIN_COMP_JOBS, MAX_COMP_JOBS, &paucEncodingBits, &uiEncodingBitsBytes, &uiExtendedWidth, &uiExtendedHeight, &iEncodingTime_ms); AZ::u8* dstMem; AZ::u32 dstPitch; dstImage->GetImagePointer(mip, dstMem, dstPitch); memcpy(dstMem, paucEncodingBits, uiEncodingBitsBytes); delete[] rgbaPixels; } return dstImage; } IImageObjectPtr ETC2Compressor::DecompressImage(IImageObjectPtr srcImage, EPixelFormat fmtDst) { //etc2Comp doesn't support decompression //Since PVRTexLib support ETC formats too. It may take over the decompression. return nullptr; } } // namespace ImageProcessing