#include "ImgEffects.h" #include "ImageData.h" #include #include "util/CatmullRom.h" #include "util/Vector.h" #include #include "PSDReader.h" #include "ImageUtils.h" #include "ImageAdjustments.h" #include "util/PerfTimer.h" USING_NS_BF; static inline float BFRound(float val) { if (val < 0) return (float) (int) (val - 0.5f); else return (float) (int) (val + 0.5f); } static uint16* CreateContourDataTable(ImageCurve* contour, float range = 1.0f) { uint16* contourData = new uint16[CONTOUR_DATA_SIZE]; for (int i = 0; i < CONTOUR_DATA_SIZE; i++) { float aX = i / (float) (CONTOUR_DATA_SIZE - 1); float tableX = std::min(1.0f, aX / range); float yVal = contour->GetVal(tableX * 255.0f) / 255.0f; contourData[i] = BFClamp((int) (yVal * (GRADIENT_DATA_SIZE - 1) + 0.5f), 0, GRADIENT_DATA_SIZE - 1); } return contourData; } static uint32* CreateGradientDataTable(ImageGradient* colorGradient) { uint32* gradientData = new uint32[GRADIENT_DATA_SIZE]; for (int i = 0; i < GRADIENT_DATA_SIZE; i++) { float yVal = i / (float) (GRADIENT_DATA_SIZE - 1); uint32 color = 0; for (int colorIdx = 0; colorIdx < 4; colorIdx++) { float gradientPos = 1.0f - yVal; if ((colorIdx == 3) && (colorGradient[colorIdx].mPoints.size() > 2)) { // Strange effect where alpha gradient never fully reaches end when there's more than one stop gradientPos = std::min(gradientPos, 0.95f); } color |= (colorGradient[colorIdx].GetVal(gradientPos * colorGradient[colorIdx].mXSize)) << (colorIdx * 8); } gradientData[i] = color; } return gradientData; } ImageCurvePoint::ImageCurvePoint() { mSpline = NULL; mIsSplineOwner = false; } ImageCurvePoint::~ImageCurvePoint() { if (mIsSplineOwner) mSpline = NULL; } void ImageCurve::Init() { CubicFuncSpline* curSpline = NULL; for (int i = 0; i < (int) mPoints.size(); i++) { ImageCurvePoint* pt = &mPoints[i]; if (pt->mIsCorner) { if (curSpline != NULL) { curSpline->AddPt(pt->mX, pt->mY); curSpline = NULL; } } else { if (curSpline == NULL) { curSpline = new CubicFuncSpline(); pt->mIsSplineOwner = true; } pt->mSpline = curSpline; curSpline->AddPt(pt->mX, pt->mY); } } /*{ std::fstream stream("c:\\temp\\curve.csv", std::ios::out); for (int i = 0; i < (int) 255; i++) { float aVal = GetVal(i); stream << aVal << std::endl; } BF_ASSERT(stream.is_open()); stream.flush(); } _asm nop;*/ } float ImageCurve::GetVal(float x) { ImageCurvePoint* prevPt = NULL; for (int i = 0; i < (int) mPoints.size(); i++) { ImageCurvePoint* nextPt = &mPoints[i]; if (x == nextPt->mX) return nextPt->mY; if ((x <= nextPt->mX) && (prevPt != NULL)) { if (prevPt->mSpline != NULL) return prevPt->mSpline->Evaluate(x); //return BFClamp(prevPt->mSpline->Evaluate(x), 0.0f, 255.0f); return prevPt->mY + ((x - prevPt->mX) / (nextPt->mX - prevPt->mX)) * (nextPt->mY - prevPt->mY); } prevPt = nextPt; } return mPoints.back().mY; } bool ImageCurve::IsDefault() { return (mPoints.size() == 2) && (mPoints[0].mX == 0) && (mPoints[0].mY == 0) && (mPoints[1].mX == 255.0f) && (mPoints[1].mY == 255.0f); } static uint32 LerpColor(uint32 color1, uint32 color2, float pct) { if (color1 == color2) return color1; uint32 a = (uint32)(pct * 256.0f); uint32 oma = 256 - a; uint32 color = (((((color1 & 0x000000FF) * oma) + ((color2 & 0x000000FF) * a)) >> 8) & 0x000000FF) | (((((color1 & 0x0000FF00) * oma) + ((color2 & 0x0000FF00) * a)) >> 8) & 0x0000FF00) | (((((color1 & 0x00FF0000) * oma) + ((color2 & 0x00FF0000) * a)) >> 8) & 0x00FF0000) | (((((color1 >> 24) & 0xFF) * oma) + (((color2 >> 24) & 0xFF) * a) & 0x0000FF00) << 16); return color; } int ImageGradient::GetVal(float x) { if (mSpline.mInputPoints.size() == 0) { int n = (int)mPoints.size() - 1; mSpline.AddPt((float) mPoints[0].mValue); for (int i = 0; i < (int) mPoints.size(); i++) mSpline.AddPt((float) mPoints[i].mValue); mSpline.AddPt((float) mPoints[n].mValue); /*{ std::fstream stream("c:\\temp\\curve.csv", std::ios::out); for (int i = 0; i < (int) 256; i++) { int aVal = GetVal((float) i * 4096 / 256); stream << aVal << std::endl; } BF_ASSERT(stream.is_open()); stream.flush(); } _asm nop;*/ } for (int i = 0; i < (int) mPoints.size(); i++) { if (x < mPoints[i].mX) { if (i == 0) return mPoints[i].mValue; float prevVal = (float) mPoints[i - 1].mValue; float nextVal = (float) mPoints[i].mValue; float aPct = (x - mPoints[i - 1].mX) / (mPoints[i].mX - mPoints[i - 1].mX); float linearVal = (prevVal * (1.0f - aPct)) + (nextVal * aPct); if ((mSmoothness == 0) || (mPoints.size() <= 2)) return (int) (linearVal + 0.5f); float curveVal = mSpline.Evaluate(i + aPct); float result = (curveVal * mSmoothness) + (linearVal * (1.0f - mSmoothness)); if (prevVal < nextVal) result = BFClamp(result, prevVal, nextVal); else result = BFClamp(result, nextVal, prevVal); return (int) (result + 0.5f); } } return mPoints.back().mValue; } ImageEffects::ImageEffects() { mSwapImages[0] = NULL; mSwapImages[1] = NULL; } ImageEffects::~ImageEffects() { for (int i = 0; i < (int) mImageEffectVector.size(); i++) delete mImageEffectVector[i]; } ImageData* ImageEffects::GetDestImage(ImageData* usingImage) { if ((mSwapImages[0] != usingImage) && (mSwapImages[0] != NULL)) return mSwapImages[0]; if ((mSwapImages[1] != usingImage) && (mSwapImages[1] != NULL)) return mSwapImages[1]; ImageData* anImage = new ImageData(); anImage->mStride = usingImage->mStride; anImage->mWidth = usingImage->mWidth; anImage->mHeight = usingImage->mHeight; anImage->mBits = new uint32[anImage->mWidth*anImage->mHeight]; if (mSwapImages[0] == NULL) mSwapImages[0] = anImage; else mSwapImages[1] = anImage; return anImage; } #define BOXBLUR_IN(x) (((x < 0) || (x >= width)) ? edgeValue : in[inIndex + x]) static void BoxBlur(uint32* in, uint32* out, int width, int height, float radius, uint32 edgeValue) { AutoPerf gPerf("ImgEffects - BoxBlur"); int widthMinus1 = width-1; int r = (int)radius; int tableSize = 2*r+1; float frac = radius - r; int a = (int) (frac * 256); int oma = 256 - a; int div = (2*r+1)*256 + a*2; int inIndex = 0; if (radius > 1) { for ( int y = 0; y < height; y++ ) { int outIndex = y; uint32 ta = 0; for ( int i = -r; i <= r; i++ ) { int rgb = BOXBLUR_IN(i); ta += rgb * 256; } ta += a * BOXBLUR_IN(-r-1); ta += a * BOXBLUR_IN(r+1); for ( int x = 0; x < width; x++ ) { out[outIndex] = ta / div; uint32 r1Value = edgeValue; uint32 r2Value = edgeValue; int r1 = x+r+1; int r2 = r1+1; if (r2 < width) { r1Value = in[inIndex + r1]; r2Value = in[inIndex + r2]; } else if (r1 < width) { r1Value = in[inIndex + r1]; } uint32 l1Value = edgeValue; uint32 l2Value = edgeValue; int l1 = x-r-1; int l2 = l1+1; if (l1 >= 0) { l1Value = in[inIndex + l1]; l2Value = in[inIndex + l2]; } else if (l2 >= 0) { l2Value = in[inIndex + l2]; } int rgbL = (l1Value * a) + (l2Value * oma); int rgbR = (r1Value * oma) + (r2Value * a); ta += rgbR; ta -= rgbL; outIndex += height; } inIndex += width; } } else { for ( int y = 0; y < height; y++ ) { int outIndex = y; for ( int x = 0; x < width; x++ ) { int r = x+1; if (r > widthMinus1) r = widthMinus1; int l = x-1; if (l < 0) l = 0; int rgbL = in[inIndex+l] * a; int rgbR = in[inIndex+r] * a; int rgbM = in[inIndex+x] * 256; out[outIndex] = (rgbL + rgbM + rgbR) / div; outIndex += height; } inIndex += width; } } } float GetSoftBlurRadius(float origRadius, float radiusLeft) { //float radiusOffset = 1.90f - 0.7f * std::min(1.0f, origRadius / 10); //return radiusLeft - radiusOffset; if (radiusLeft == 0) return 0; float radiusOffset = 1.2f; float blurRadius = radiusLeft - radiusOffset; if (radiusLeft <= 2) blurRadius = 0.5f; return blurRadius; } float GetSoftBlurRadius(float origRadius) { return GetSoftBlurRadius(origRadius, origRadius); } void SoftBlurInit(ImageData* inImage, ImageData* outImage, bool invert) { uint32* in = inImage->mBits; uint32* out = outImage->mBits; int iw = inImage->mWidth; int ih = inImage->mHeight; int ow = outImage->mWidth; int oh = outImage->mHeight; int ox = inImage->mX - outImage->mX; int oy = inImage->mY - outImage->mY; if (!invert) { for (int y = 0; y < oh; y++) for (int x = 0; x < ow; x++) out[x+y*ow] = 0; for (int y = 0; y < ih; y++) { for (int x = 0; x < iw; x++) { int anAlpha = in[x+y*iw]>>24; out[(x+ox)+(y+oy)*ow] = anAlpha * 256; } } } else { for (int y = 0; y < oh; y++) for (int x = 0; x < ow; x++) out[x+y*ow] = 255*256; for (int y = 0; y < ih; y++) { for (int x = 0; x < iw; x++) { int anAlpha = in[x+y*iw]>>24; out[(x+ox)+(y+oy)*ow] = (255 - (anAlpha)) * 256; } } } } void SoftBlur(uint32* data, int w, int h, float radius, uint32 defaultValue) { uint32* tempBuffer = new uint32[w*h]; int expandedX = 0; int expandedY = 0; int itrCount = (radius < 0.9f) ? 1 : 2; float d = radius / itrCount; if (d > 0) { for (int i = 0; i < itrCount; i++) { BoxBlur(data, tempBuffer, w, h, d, defaultValue); BoxBlur(tempBuffer, data, h, w, d, defaultValue); } } delete [] tempBuffer; } static int gAlphaCheckOfs [][2] = {{-1, -1}, {0, -1}, {1, -1}, {-1, 0}, {1, 0}, {-1, 1}, {0, 1}, {1, 1}}; static int gAlphaScaleVals [] = {358, 254, 358, 254, 256, 358, 254, 358}; static int gKernelOfsFwd[][2] = { {-1, -2}, {1, -2}, {-2, -1}, {-1, -1}, {0, -1}, {1, -1}, {2, -1}, {-1, 0}}; static int gKernelValsFwd[] = {567, 567, 567, 358, 254, 358, 567, 254}; static int gKernelOfsRev[][2] = { {1, 0}, {-2, 1}, {-1, 1}, {0, 1}, {1, 1}, {2, 1}, {-1, 2}, {1, 2}}; static int gKernelValsRev[] = { 254, 567, 358, 254, 358, 567, 567, 567}; static void ChamferedDistanceTransformInit(ImageData* inImage, ImageData* outImage, bool invert, int softSize = 0) { AutoPerf gPerf("ImgEffects - ChamferedDistanceTransformInit"); uint32* in = inImage->mBits; uint32* out = outImage->mBits; int iw = inImage->mWidth; int ih = inImage->mHeight; int ow = outImage->mWidth; int oh = outImage->mHeight; int ox = inImage->mX - outImage->mX; int oy = inImage->mY - outImage->mY; int inf = (ow+oh)*564; int alphaCheckCount = sizeof(gAlphaScaleVals) / sizeof(int); if (!invert) { for (int y = 0; y < oh; y++) for (int x = 0; x < ow; x++) out[x+y*ow] = inf; for (int y = 0; y < ih; y++) { for (int x = 0; x < iw; x++) { int anAlpha = in[x+y*iw]>>24; if (anAlpha != 0) { int aVal = (255 - anAlpha); out[(x+ox)+(y+oy)*ow] = aVal + aVal / 32; } else out[(x+ox)+(y+oy)*ow] = inf; } } } else { for (int y = 0; y < oh; y++) for (int x = 0; x < ow; x++) out[x+y*ow] = 0; if (softSize != 0) { for (int y = 0; y < ih; y++) { for (int x = 0; x < iw; x++) { int anAlpha = in[x+y*iw]>>24; int aVal; if (anAlpha > 128) aVal = (anAlpha - 128) * (softSize + 1)*2; else aVal = 0; out[(x+ox)+(y+oy)*ow] = aVal; } } } else { for (int y = 0; y < ih; y++) { for (int x = 0; x < iw; x++) { int anAlpha = in[x+y*iw]>>24; if (anAlpha == 255) { out[(x+ox)+(y+oy)*ow] = inf; } else { out[(x+ox)+(y+oy)*ow] = anAlpha + anAlpha / 32; } } } } } } static void ChamferedDistanceTransformSlow(uint32* out, int width, int height, int startX, int startY, int endX, int endY) { AutoPerf gPerf("ImgEffects - ChamferedDistanceTransform"); int kernelCountFwd = sizeof(gKernelValsFwd) / sizeof(int); int kernelCountRev = sizeof(gKernelValsRev) / sizeof(int); for (int y = startY; y < endY; y++) { for (int x = startX; x < endX; x++) { for (int kernIdx = 0; kernIdx < kernelCountFwd; kernIdx++) { int cx = BFClamp(x + gKernelOfsFwd[kernIdx][0], 0, width - 1); int cy = BFClamp(y + gKernelOfsFwd[kernIdx][1], 0, height - 1); int aVal = out[cx+cy*width] + gKernelValsFwd[kernIdx]; if (aVal < (int) out[x+y*width]) out[x+y*width] = aVal; } } } for (int y = endY - 1; y >= startY; y--) { for (int x = endX - 1; x >= startX; x--) { for (int kernIdx = 0; kernIdx < kernelCountRev; kernIdx++) { int cx = BFClamp(x + gKernelOfsRev[kernIdx][0], 0, width - 1); int cy = BFClamp(y + gKernelOfsRev[kernIdx][1], 0, height - 1); int aVal = out[cx+cy*width] + gKernelValsRev[kernIdx]; if (aVal < (int) out[x+y*width]) out[x+y*width] = aVal; } } } } static void ChamferedDistanceTransform(uint32* out, int width, int height) { AutoPerf gPerf("ImgEffects - ChamferedDistanceTransform"); if ((width < 4) || (height < 4)) { ChamferedDistanceTransformSlow(out, width, height, 0, 0, width, height); return; } // Do 2 pixel border (where we have to clamp) ChamferedDistanceTransformSlow(out, width, height, 0, 0, width, 2); ChamferedDistanceTransformSlow(out, width, height, 0, height-2, width, 2); ChamferedDistanceTransformSlow(out, width, height, 0, 2, 2, height - 2); ChamferedDistanceTransformSlow(out, width, height, width - 2, 2, 2, height - 2); // Do inner region (no clamping) int kernelCountFwd = sizeof(gKernelValsFwd) / sizeof(int); int kernelCountRev = sizeof(gKernelValsRev) / sizeof(int); int aStartX = 2; int aStartY = 2; int aEndX = width - 2; int aEndY = height - 2; for (int y = aStartY; y < aEndY; y++) { for (int x = aStartX; x < aEndX; x++) { for (int kernIdx = 0; kernIdx < kernelCountFwd; kernIdx++) { int cx = x + gKernelOfsFwd[kernIdx][0]; int cy = y + gKernelOfsFwd[kernIdx][1]; int aVal = out[cx+cy*width] + gKernelValsFwd[kernIdx]; if (aVal < (int) out[x+y*width]) out[x+y*width] = aVal; } } } for (int y = aEndY - 1; y >= aStartY; y--) { for (int x = aEndX - 1; x >= aStartX; x--) { for (int kernIdx = 0; kernIdx < kernelCountRev; kernIdx++) { int cx = x + gKernelOfsRev[kernIdx][0]; int cy = y + gKernelOfsRev[kernIdx][1]; int aVal = out[cx+cy*width] + gKernelValsRev[kernIdx]; if (aVal < (int) out[x+y*width]) out[x+y*width] = aVal; } } } } #define IN_PIXEL(xval,yval) in[(xval) + (yval)*width] #define IN_PIXEL_YX(yval,xval) in[(xval) + (yval)*width] static void CreateNormalMap(uint32* in, uint32* out, int width, int height, float depth, Vector3* dotVector) { AutoPerf gPerf("ImgEffects - CreateNormalMap"); for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { // surrounding pixels const uint32 topLeft = IN_PIXEL_YX(std::max(y - 1, 0), std::max(x - 1, 0)); const uint32 top = IN_PIXEL_YX(std::max(y - 1, 0), x); const uint32 topRight = IN_PIXEL_YX(std::max(y - 1, 0), std::min(x + 1, width - 1)); const uint32 right = IN_PIXEL_YX(y, std::min(x + 1, width - 1)); const uint32 bottomRight = IN_PIXEL_YX(std::min(y + 1, height - 1), std::min(x + 1, width - 1)); const uint32 bottom = IN_PIXEL_YX(std::min(y + 1, height - 1), x); const uint32 bottomLeft = IN_PIXEL_YX(std::min(y + 1, height - 1), std::max(x - 1, 0)); const uint32 left = IN_PIXEL_YX(y, std::max(x - 1, 0)); // their intensities const float tl = (float)(topLeft); const float t = (float)(top); const float tr = (float)(topRight); const float r = (float)(right); const float br = (float)(bottomRight); const float b = (float)(bottom); const float bl = (float)(bottomLeft); const float l = (float) (left); // sobel filter float dX = (tr + 2.0f * r + br) - (tl + 2.0f * l + bl); float dY = (bl + 2.0f * b + br) - (tl + 2.0f * t + tr); float dZ = depth * 10 * 0x100; float len = sqrt(dX*dX + dY*dY + dZ*dZ); dX /= len; dY /= len; dZ /= len; if (dotVector != NULL) { Vector3 aNorm(dX, dY, dZ); float dot = Vector3::Dot(*dotVector, aNorm); out[x+y*width] = (uint32) (0xFF00 * dot) + 0x100000; } else { out[x+y*width] = ((int) (dX * 0x7F + 0x80 + 0.5f)) | ((int) (dY * 0x7F + 0x80 + 0.5f) << 8) | ((int) (dZ * 0xFF) << 16) | 0xFF000000; } } } } static void AntiAliasIndices(uint32* in, int width, int height) { int w = width; int h = height; for (int y = 0; y < h - 1; y++) { for (int x = 0; x < w - 1; x++) { int val1 = in[x+y*w]; int val2 = in[(x+1)+y*w]; int val3 = in[x+(y+1)*w]; int val4 = in[(x+1)+(y+1)*w]; in[x+y*w] = ((val1 * 114) + (val2 * 57) + (val3 * 57) + (val4 * 28)) / 256; } } } static inline void BlendColors(PackedColor* destColor, PackedColor* underColor, PackedColor* overColor) { int a = 255 - overColor->a; int oma = 255 - a; destColor->r = ((underColor->r * a) + (overColor->r * oma)) / 255; destColor->g = ((underColor->g * a) + (overColor->g * oma)) / 255; destColor->b = ((underColor->b * a) + (overColor->b * oma)) / 255; } template void ApplyBlendingRange(ImageData* origImage, ImageData* destImage, ImageData* checkImage, int rangeStart, int rangeEnd, CompareFunctor functor) { if ((rangeStart == 0) && (rangeEnd == 0xFF)) return; int aStartX = std::max(checkImage->mX, origImage->mX); int aStartY = std::max(checkImage->mY, origImage->mY); int aEndX = std::min(checkImage->mX + checkImage->mWidth, origImage->mX + origImage->mWidth); int aEndY = std::min(checkImage->mY + checkImage->mHeight, origImage->mY + origImage->mHeight); if (rangeStart <= rangeEnd) { for (int y = aStartY; y < aEndY; y++) { PackedColor* aDestColor = (PackedColor*) (destImage->mBits + (aStartX - destImage->mX) + ((y - destImage->mY) * destImage->mWidth)); PackedColor* origColor = (PackedColor*) (origImage->mBits + (aStartX - origImage->mX) + ((y - origImage->mY) * origImage->mWidth)); PackedColor* checkColor = (PackedColor*) (checkImage->mBits + (aStartX - checkImage->mX) + ((y - checkImage->mY) * checkImage->mWidth)); for (int x = aStartX; x < aEndX; x++) { if ((functor(*checkColor) < rangeStart) || (functor(*checkColor) > rangeEnd)) *aDestColor = *origColor; aDestColor++; origColor++; checkColor++; } } } else // Inverted range { for (int y = aStartY; y < aEndY; y++) { PackedColor* aDestColor = (PackedColor*) (destImage->mBits + (aStartX - destImage->mX) + ((y - destImage->mY) * destImage->mWidth)); PackedColor* origColor = (PackedColor*) (origImage->mBits + (aStartX - origImage->mX) + ((y - origImage->mY) * origImage->mWidth)); PackedColor* checkColor = (PackedColor*) (checkImage->mBits + (aStartX - checkImage->mX) + ((y - checkImage->mY) * checkImage->mWidth)); for (int x = aStartX; x < aEndX; x++) { if ((functor(*checkColor) < rangeStart) && (functor(*checkColor) > rangeEnd)) *aDestColor = *origColor; aDestColor++; origColor++; checkColor++; } } } } ImageData* ImageEffects::FlattenInto(ImageData* dest, PSDLayerInfo* srcLayer, ImageData* srcImage, ImageData* knockoutBottom, ImageData* insideImage) { AutoPerf gPerf("ImageEffects::FlattenInto"); bool hasComplexBlending = srcLayer->mBlendMode != 'norm'; BF_ASSERT((dest == NULL) || (!dest->mAlphaPremultiplied)); BF_ASSERT(!srcLayer->mAlphaPremultiplied); ImageData* aSrcImage = srcImage; if (srcLayer->mImageAdjustment != NULL) { if (((srcLayer->mLayerMask != NULL) && (srcLayer->mLayerMaskEnabled)) || (srcLayer->mVectorMask != NULL)) { if (srcLayer->mWidth == 0) { aSrcImage = new ImageData(); if (srcLayer->mLayerMaskDefault == 255) aSrcImage->CreateNew(0, 0, srcLayer->mPSDReader->mWidth, srcLayer->mPSDReader->mHeight, false); else aSrcImage->CreateNew(srcLayer->mLayerMaskX, srcLayer->mLayerMaskY, srcLayer->mLayerMaskWidth, srcLayer->mLayerMaskHeight, false); aSrcImage->Fill(0xFFFFFFFF); srcLayer->ApplyMask(aSrcImage); } } else { // No mask - just directly apply the effects ImageData* aDestImage = srcLayer->mImageAdjustment->CreateAdjustedImage(srcLayer, dest); ImageEffectCtx aCtx; aCtx.mOrigImage = NULL; aCtx.mBlendX = dest->mX; aCtx.mBlendY = dest->mY; aCtx.mBlendWidth = dest->mWidth; aCtx.mBlendHeight = dest->mHeight; aCtx.mInnerImage = NULL; aCtx.mOuterImage = NULL; aCtx.mLayerInfo = srcLayer; aCtx.mLayerImage = NULL; return aDestImage; } } int borderSize = 0; bool hasInnerEffect = false; bool hasOuterEffect = false; bool needsOrigBits = false; for (int i = 0; i < (int) mImageEffectVector.size(); i++) { BaseImageEffect* anEffect = mImageEffectVector[i]; borderSize = std::max(borderSize, anEffect->GetNeededBorderSize()); int mixType = anEffect->GetMixType(); hasInnerEffect |= (mixType == IMAGEMIX_INNER) || (mixType == IMAGEMIX_OVER); hasOuterEffect |= (mixType == IMAGEMIX_OUTER) || (mixType == IMAGEMIX_OVER); needsOrigBits |= anEffect->NeedsOrigBits(this); hasComplexBlending |= anEffect->mBlendMode != 'Nrml'; } bool hasBlendingRanges = (srcLayer->mBlendingRangeSourceStart != 0x00000000) || (srcLayer->mBlendingRangeSourceEnd != 0xFFFFFFFF) || (srcLayer->mBlendingRangeDestStart != 0x00000000) || (srcLayer->mBlendingRangeDestEnd != 0xFFFFFFFF); bool doPostOpacity = hasComplexBlending || hasOuterEffect; uint8* aMask = NULL; //hasInnerEffect = true; if (insideImage != NULL) hasInnerEffect = true; hasInnerEffect = true; hasOuterEffect = true; needsOrigBits |= srcLayer->mLayerMaskHidesEffects; if (!srcLayer->mTransparencyShapesLayer) hasOuterEffect = false; int minDestX = aSrcImage->mX - borderSize; int maxDestX = aSrcImage->mX + aSrcImage->mWidth + borderSize; int minDestY = aSrcImage->mY - borderSize; int maxDestY = aSrcImage->mY + aSrcImage->mHeight + borderSize; if (dest != NULL) { minDestX = std::min(dest->mX, minDestX); maxDestX = std::max(dest->mX + dest->mWidth, maxDestX); minDestY = std::min(dest->mY, minDestY); maxDestY = std::max(dest->mY + dest->mHeight, maxDestY); } ImageEffectCtx aCtx; aCtx.mOrigImage = NULL; aCtx.mBlendX = minDestX; aCtx.mBlendY = minDestY; aCtx.mBlendWidth = maxDestX - minDestX; aCtx.mBlendHeight = maxDestY - minDestY; aCtx.mInnerImage = NULL; aCtx.mOuterImage = NULL; aCtx.mLayerInfo = srcLayer; aCtx.mLayerImage = aSrcImage; ImageData* aDestImage = NULL; int minCopyX = aSrcImage->mX - borderSize; int maxCopyX = aSrcImage->mX + aSrcImage->mWidth + borderSize; int minCopyY = aSrcImage->mY - borderSize; int maxCopyY = aSrcImage->mY + aSrcImage->mHeight + borderSize; // This gets set if we need the 'dest' initialized with bits bool needsDestInit = (!hasOuterEffect) || (!hasInnerEffect); for (int i = 0; i < 4; i++) { bool needed = ((i == 0) && (hasInnerEffect)) || ((i == 1) && (hasOuterEffect)) || ((i == 2) && (needsOrigBits)) || ((i == 3) && (needsDestInit)); if (!needed) continue; ImageData* effectImage = new ImageData(); effectImage->mX = aCtx.mBlendX; effectImage->mY = aCtx.mBlendY; effectImage->CreateNew(aCtx.mBlendWidth, aCtx.mBlendHeight); if (dest != NULL) { int minX = dest->mX; int maxX = dest->mX + dest->mWidth; int minY = dest->mY; int maxY = dest->mY + dest->mHeight; if ((i == 0) && (insideImage != NULL)) { CopyImageBits(effectImage, insideImage); } else if ((i == 0) && (srcLayer->mKnockout == 1)) { // Shallow knockout if (knockoutBottom != NULL) { for (int y = knockoutBottom->mY; y < knockoutBottom->mY + knockoutBottom->mHeight; y++) { for (int x = knockoutBottom->mX; x < knockoutBottom->mX + knockoutBottom->mWidth; x++) { effectImage->mBits[(x - effectImage->mX) + (y - effectImage->mY)*effectImage->mWidth] = knockoutBottom->mBits[(x - knockoutBottom->mX) + (y - knockoutBottom->mY)*knockoutBottom->mWidth]; } } } } else if ((i == 0) && (srcLayer->mKnockout == 2)) { // Deep knockout - leave blank } else { for (int y = minY; y < maxY; y++) { for (int x = minX; x < maxX; x++) { effectImage->mBits[(x - effectImage->mX) + (y - effectImage->mY)*effectImage->mWidth] = dest->mBits[(x - dest->mX) + (y - dest->mY)*dest->mWidth]; } } } } if (i == 0) aCtx.mInnerImage = effectImage; else if (i == 1) aCtx.mOuterImage = effectImage; else if (i == 2) aCtx.mOrigImage = effectImage; else aDestImage = effectImage; } if (aDestImage == NULL) { aDestImage = new ImageData(); aDestImage->mX = aCtx.mBlendX; aDestImage->mY = aCtx.mBlendY; aDestImage->CreateNew(aCtx.mBlendWidth, aCtx.mBlendHeight); } if (aCtx.mInnerImage != NULL) { if (insideImage != NULL) { //BlendImage(aCtx.mInnerImage, aSrcImage, insideImage->mX - aCtx.mBlendX, insideImage->mY - aCtx.mBlendY, 1.0f, 'Nrml'); } else if (srcLayer->mImageAdjustment != NULL) { srcLayer->mImageAdjustment->ApplyImageAdjustment(srcLayer, aCtx.mInnerImage); CrossfadeImage(dest, aCtx.mInnerImage, srcLayer->mFillOpacity / 255.0f); } else { int blendMode = srcLayer->mBlendMode; float anAlpha = srcLayer->mFillOpacity / 255.0f; if (srcLayer->mBlendInteriorEffectsAsGroup) anAlpha = 1.0f; if (!doPostOpacity) anAlpha *= (srcLayer->mOpacity / 255.0f); BlendImage(aCtx.mInnerImage, aSrcImage, aSrcImage->mX - aCtx.mBlendX, aSrcImage->mY - aCtx.mBlendY, anAlpha, blendMode, true); } } else { float anAlpha = srcLayer->mFillOpacity / 255.0f; if (srcLayer->mBlendInteriorEffectsAsGroup) anAlpha = 1.0f; if (!doPostOpacity) anAlpha *= (srcLayer->mOpacity / 255.0f); BlendImage(aDestImage, aSrcImage, aSrcImage->mX - aCtx.mBlendX, aSrcImage->mY - aCtx.mBlendY, anAlpha, srcLayer->mBlendMode, aCtx.mOuterImage != NULL); } if ((srcLayer->mLayerMaskHidesEffects) && (srcLayer->mLayerMaskEnabled)) { aMask = new uint8[aCtx.mBlendWidth * aCtx.mBlendHeight]; memset(aMask, srcLayer->mLayerMaskDefault, aCtx.mBlendWidth * aCtx.mBlendHeight); ImageData* anImage = aCtx.mOrigImage; int maskStartX = std::max(srcLayer->mLayerMaskX, anImage->mX); int maskStartY = std::max(srcLayer->mLayerMaskY, anImage->mY); int maskEndX = std::min(srcLayer->mLayerMaskX + srcLayer->mLayerMaskWidth, anImage->mX + anImage->mWidth); int maskEndY = std::min(srcLayer->mLayerMaskY + srcLayer->mLayerMaskHeight, anImage->mY + anImage->mHeight); for (int y = maskStartY; y < maskEndY; y++) { uint8* maskSrc = srcLayer->mLayerMask + (y - srcLayer->mLayerMaskY)*srcLayer->mLayerMaskWidth; uint8* maskDest = aMask + (y - anImage->mY)*anImage->mWidth; for (int x = maskStartX; x < maskEndX; x++) maskDest[x - anImage->mX] = maskSrc[x - srcLayer->mLayerMaskX]; } } for (int i = 0; i < (int) mImageEffectVector.size(); i++) { BaseImageEffect* anEffect = mImageEffectVector[i]; if (!anEffect->mInitialized) { anEffect->Init(); anEffect->mInitialized = true; } AutoPerf gPerf("ImageEffects::FlattenInto ImageEffect"); anEffect->Apply(&aCtx); } // Did we create a temporary source image (for adjustment layers, primary) if (aSrcImage != srcImage) { //delete aSrcImage; //aSrcImage = srcImage; // Add back in the destination alpha that was factored out during adjustment image creation //MultiplyImageAlpha(aSrcImage, dest); } //aCtx.mOuterImage->mBits[0] = 0xFFFFFFFF; //aCtx.mOuterImage->mBits[aCtx.mBlendWidth*aCtx.mBlendHeight-1] = 0xFFFFFFFF; if (srcLayer->mBlendInteriorEffectsAsGroup) { for (int y = 0; y < aSrcImage->mHeight; y++) { PackedColor* srcColor = (PackedColor*) (aSrcImage->mBits + y*aSrcImage->mWidth); for (int x = 0; x < aSrcImage->mWidth; x++) { srcColor->a = (srcColor->a * srcLayer->mFillOpacity) / 255; srcColor++; } } } if (aCtx.mOuterImage != NULL) { //TODO: Do this right... /*for (int x = 0; x < aSrcImage->mWidth; x++) { for (int y = 0; y < borderSize; y++) aDestImage->mBits[x+y*aCtx.mBlendWidth] = aCtx.mOuterImage->mBits[x+y*aCtx.mBlendWidth]; for (int y = aCtx.mBlendHeight-borderSize; y < aCtx.mBlendHeight; y++) aDestImage->mBits[x+y*aCtx.mBlendWidth] = aCtx.mOuterImage->mBits[x+y*aCtx.mBlendWidth]; } for (int y = borderSize; y < aCtx.mBlendHeight-borderSize; y++) { for (int x = 0; x < borderSize; x++) aDestImage->mBits[x+y*aCtx.mBlendWidth] = aCtx.mOuterImage->mBits[x+y*aCtx.mBlendWidth]; for (int x = aCtx.mBlendWidth-borderSize; x < aCtx.mBlendWidth; x++) aDestImage->mBits[x+y*aCtx.mBlendWidth] = aCtx.mOuterImage->mBits[x+y*aCtx.mBlendWidth]; }*/ for (int y = aCtx.mBlendY; y < aCtx.mBlendY + aCtx.mBlendHeight; y++) { for (int x = aCtx.mBlendX; x < aCtx.mBlendX + aCtx.mBlendWidth; x++) { aDestImage->mBits[(x-aCtx.mBlendX)+(y-aCtx.mBlendY)*aCtx.mBlendWidth] = aCtx.mOuterImage->mBits[(x-aCtx.mBlendX)+(y-aCtx.mBlendY)*aCtx.mBlendWidth]; } } /*int aSize = aCtx.mBlendWidth*aCtx.mBlendHeight; for (int i = 0; i < aSize; i++) aDestImage->mBits[i] = aCtx.mOuterImage->mBits[i];*/ if (aCtx.mInnerImage == NULL) { for (int y = 0; y < aSrcImage->mHeight; y++) { PackedColor* srcColor = (PackedColor*) (aSrcImage->mBits + y*aSrcImage->mWidth); PackedColor* aDestColor = (PackedColor*) (aDestImage->mBits + borderSize + (y + borderSize) * aDestImage->mWidth); PackedColor* effectOutsideColor = (PackedColor*) (aCtx.mOuterImage->mBits + borderSize + ((y + borderSize) * aCtx.mBlendWidth)); for (int x = 0; x < aSrcImage->mWidth; x++) { int a = srcColor->a; int oma = 255 - a; int newDestAlpha = ((aDestColor->a * a) + (effectOutsideColor->a * oma)) / 255; if (newDestAlpha != 0) { int ca = (255 * aDestColor->a * a) / (aDestColor->a * a + effectOutsideColor->a * oma); int coma = 255 - ca; aDestColor->a = newDestAlpha; aDestColor->r = ((aDestColor->r * ca) + (effectOutsideColor->r * coma)) / 255; aDestColor->g = ((aDestColor->g * ca) + (effectOutsideColor->g * coma)) / 255; aDestColor->b = ((aDestColor->b * ca) + (effectOutsideColor->b * coma)) / 255; } srcColor++; aDestColor++; effectOutsideColor++; } } } } if (aCtx.mInnerImage != NULL) { if (aCtx.mOuterImage != NULL) { if (aMask != NULL) { for (int y = 0; y < aSrcImage->mHeight; y++) { uint8* maskData = (aMask + borderSize + ((y + borderSize) * aCtx.mBlendWidth)); PackedColor* srcColor = (PackedColor*) (aSrcImage->mBits + y*aSrcImage->mWidth); PackedColor* aDestColor = (PackedColor*) (aDestImage->mBits + borderSize + (y + borderSize) * aDestImage->mWidth); PackedColor* effectInsideColor = (PackedColor*) (aCtx.mInnerImage->mBits + borderSize + ((y + borderSize) * aCtx.mBlendWidth)); PackedColor* effectOutsideColor = (PackedColor*) (aCtx.mOuterImage->mBits + borderSize + ((y + borderSize) * aCtx.mBlendWidth)); PackedColor* origColor = (PackedColor*) (aCtx.mOrigImage->mBits + borderSize + ((y + borderSize) * aCtx.mBlendWidth)); for (int x = 0; x < aSrcImage->mWidth; x++) { /*if ((aCtx.mBlendX + x == 60) && (aCtx.mBlendY + y == 60)) { _asm nop; }*/ uint32 insidePct = srcColor->a * (*maskData); uint32 outsidePct = (255 - srcColor->a) * (*maskData); uint32 origPct = 255 * (255 - *maskData); uint32 insideContrib = effectInsideColor->a * insidePct; uint32 outsideContrib = effectOutsideColor->a * outsidePct; uint32 origContrib = origColor->a * origPct; uint32 totalContrib = insideContrib + outsideContrib + origContrib; int newDestAlpha = (insideContrib + outsideContrib + origContrib) / 255 / 255; if (newDestAlpha != 0) { int ma = *maskData; int moma = 255 - ma; aDestColor->r = ((insideContrib * effectInsideColor->r) + (outsideContrib * effectOutsideColor->r) + (origContrib * origColor->r)) / totalContrib; aDestColor->g = ((insideContrib * effectInsideColor->g) + (outsideContrib * effectOutsideColor->g) + (origContrib * origColor->g)) / totalContrib; aDestColor->b = ((insideContrib * effectInsideColor->b) + (outsideContrib * effectOutsideColor->b) + (origContrib * origColor->b)) / totalContrib; } aDestColor->a = newDestAlpha; origColor++; maskData++; srcColor++; aDestColor++; effectInsideColor++; effectOutsideColor++; } } } else { for (int y = 0; y < aSrcImage->mHeight; y++) { int ctxOffset = (aSrcImage->mX - aCtx.mBlendX) + (aSrcImage->mY - aCtx.mBlendY + y)*aCtx.mBlendWidth; PackedColor* srcColor = (PackedColor*) (aSrcImage->mBits + y*aSrcImage->mWidth); PackedColor* aDestColor = (PackedColor*) (aDestImage->mBits + ctxOffset); PackedColor* effectInsideColor = (PackedColor*) (aCtx.mInnerImage->mBits + ctxOffset); PackedColor* effectOutsideColor = (PackedColor*) (aCtx.mOuterImage->mBits + ctxOffset); for (int x = 0; x < aSrcImage->mWidth; x++) { if (srcColor->a == 0) { *aDestColor = *effectOutsideColor; } else { int a = srcColor->a; int oma = 255 - a; int newDestAlpha = ((effectInsideColor->a * a) + (effectOutsideColor->a * oma)) / 255; if (newDestAlpha != 0) { int ca = (255 * effectInsideColor->a * a) / (effectInsideColor->a * a + effectOutsideColor->a * oma); int coma = 255 - ca; aDestColor->a = newDestAlpha; aDestColor->r = ((effectInsideColor->r * ca) + (effectOutsideColor->r * coma)) / 255; aDestColor->g = ((effectInsideColor->g * ca) + (effectOutsideColor->g * coma)) / 255; aDestColor->b = ((effectInsideColor->b * ca) + (effectOutsideColor->b * coma)) / 255; } else { *aDestColor = *effectOutsideColor; } } srcColor++; aDestColor++; effectInsideColor++; effectOutsideColor++; } } } } else { for (int y = 0; y < aSrcImage->mHeight; y++) { PackedColor* srcColor = (PackedColor*) (aSrcImage->mBits + y*aSrcImage->mWidth); PackedColor* aDestColor = (PackedColor*) (aDestImage->mBits + borderSize + (y + borderSize) * aDestImage->mWidth); PackedColor* effectInsideColor = (PackedColor*) (aCtx.mInnerImage->mBits + borderSize + ((y + borderSize) * aCtx.mBlendWidth)); for (int x = 0; x < aSrcImage->mWidth; x++) { //if (srcColor->a != 0) { int a = 255 - srcColor->a; if (!srcLayer->mTransparencyShapesLayer) a = 0; int oma = 255 - a; int newDestAlpha = ((aDestColor->a * a) + (effectInsideColor->a * oma)) / 255; if (newDestAlpha != 0) { int ca = (255 * aDestColor->a * a) / (aDestColor->a * a + effectInsideColor->a * oma); int coma = 255 - ca; aDestColor->a = newDestAlpha; aDestColor->r = ((aDestColor->r * ca) + (effectInsideColor->r * coma)) / 255; aDestColor->g = ((aDestColor->g * ca) + (effectInsideColor->g * coma)) / 255; aDestColor->b = ((aDestColor->b * ca) + (effectInsideColor->b * coma)) / 255; } } srcColor++; aDestColor++; effectInsideColor++; } } } } /*if (srcLayer->mImageAdjustment != NULL) { // Just use the "inner image" as the dest since there's no mask to apply delete aDestImage; aDestImage = aCtx.mInnerImage; aCtx.mInnerImage = NULL; }*/ if (hasBlendingRanges) { int aStartX = std::max(aSrcImage->mX, dest->mX); int aStartY = std::max(aSrcImage->mY, dest->mY); int aEndX = std::min(aSrcImage->mX + aSrcImage->mWidth, dest->mX + dest->mWidth); int aEndY = std::min(aSrcImage->mY + aSrcImage->mHeight, dest->mY + dest->mHeight); PackedColor* blendingRangeSourceStart = (PackedColor*) &(srcLayer->mBlendingRangeSourceStart); PackedColor* blendingRangeSourceEnd = (PackedColor*) &(srcLayer->mBlendingRangeSourceEnd); PackedColor* blendingRangeDestStart = (PackedColor*) &(srcLayer->mBlendingRangeDestStart); PackedColor* blendingRangeDestEnd = (PackedColor*) &(srcLayer->mBlendingRangeDestEnd); ApplyBlendingRange(dest, aDestImage, aSrcImage, blendingRangeSourceStart->r, blendingRangeSourceEnd->r, PackedColorGetR()); ApplyBlendingRange(dest, aDestImage, dest, blendingRangeDestStart->r, blendingRangeDestEnd->r, PackedColorGetR()); ApplyBlendingRange(dest, aDestImage, aSrcImage, blendingRangeSourceStart->g, blendingRangeSourceEnd->g, PackedColorGetG()); ApplyBlendingRange(dest, aDestImage, dest, blendingRangeDestStart->g, blendingRangeDestEnd->g, PackedColorGetG()); ApplyBlendingRange(dest, aDestImage, aSrcImage, blendingRangeSourceStart->b, blendingRangeSourceEnd->b, PackedColorGetB()); ApplyBlendingRange(dest, aDestImage, dest, blendingRangeDestStart->b, blendingRangeDestEnd->b, PackedColorGetB()); ApplyBlendingRange(dest, aDestImage, aSrcImage, blendingRangeSourceStart->a, blendingRangeSourceEnd->a, PackedColorGetGray()); ApplyBlendingRange(dest, aDestImage, dest, blendingRangeDestStart->a, blendingRangeDestEnd->a, PackedColorGetGray()); } if (srcLayer->mChannelMask != 0xFFFFFFFF) { for (int i = 0; i < aDestImage->mWidth*aDestImage->mHeight; i++) aDestImage->mBits[i] &= srcLayer->mChannelMask; int aStartX = dest->mX; int aStartY = dest->mY; int aEndX = dest->mX + dest->mWidth; int aEndY = dest->mY + dest->mHeight; for (int y = aStartY; y < aEndY; y++) { uint32* aDestColor = aDestImage->mBits + (aStartX - aDestImage->mX) + ((y - aDestImage->mY) * aDestImage->mWidth); uint32* origColor = dest->mBits + (aStartX - dest->mX) + ((y - dest->mY) * dest->mWidth); for (int x = aStartX; x < aEndX; x++) { *aDestColor |= (*origColor & ~srcLayer->mChannelMask); aDestColor++; origColor++; } } } if ((srcLayer->mOpacity != 255) && (doPostOpacity)) CrossfadeImage(dest, aDestImage, srcLayer->mOpacity / 255.0f); if (aSrcImage != srcImage) { // If we have a temporary source image (for an adjustment layer, for example) delete aSrcImage; } delete aMask; return aDestImage; } void ImageEffects::AddEffect(BaseImageEffect* effect) { mImageEffectVector.push_back(effect); } // BaseImageEffect::BaseImageEffect() { mContourData = NULL; mGradientData = NULL; mInitialized = false; } BaseImageEffect::~BaseImageEffect() { delete mContourData; delete mGradientData; } void BaseImageEffect::Init() { } void BaseImageEffect::Apply(ImageEffectCtx* ctx) { ImageData* effectImage = new ImageData(); effectImage->CreateNew(ctx->mBlendWidth, ctx->mBlendHeight); effectImage->mX = ctx->mBlendX; effectImage->mY = ctx->mBlendY; Apply(ctx->mLayerInfo, ctx->mLayerImage, effectImage); int mixType = GetMixType(); if ((mixType == IMAGEMIX_INNER) || (mixType == IMAGEMIX_OVER)) BlendImage(ctx->mInnerImage, effectImage, 0, 0, (float) mOpacity / 100.0f, mBlendMode); if ((mixType == IMAGEMIX_OUTER) || (mixType == IMAGEMIX_OVER)) BlendImage(ctx->mOuterImage, effectImage, 0, 0, (float) mOpacity / 100.0f, mBlendMode); delete effectImage; } int BaseImageEffect::GetMixType() // Otherwise interior { return IMAGEMIX_INNER; } int BaseImageEffect::GetNeededBorderSize() { return 0; } bool BaseImageEffect::NeedsOrigBits(ImageEffects* effects) { return false; } void ImageShadowEffect::Init() { mContourData = CreateContourDataTable(&mContour, 1.0f); } void ImageShadowEffect::Apply(PSDLayerInfo* layerInfo, ImageData* imageData, ImageData* destImageData) { int w = destImageData->mWidth; int h = destImageData->mHeight; int aSize = w*h; float spread = (float) mSpread / 100.0f; float spreadSize = (float) (int) (mSize * spread + 0.5f); int distTransSize = 0; ImageData* tempImage = new ImageData(); tempImage->CreateNew(w, h); tempImage->mX = destImageData->mX; tempImage->mY = destImageData->mY; float aRadius = (float) mSize; float aRadiusLeft = (float) mSize; bool isInside = GetMixType() == IMAGEMIX_INNER; if (spreadSize != 0) { // Do distance transform first to "grow" area float distLenOffset = spreadSize / 20.0f; float distLen = spreadSize - 1.0f + distLenOffset; ChamferedDistanceTransformInit(imageData, tempImage, isInside); ChamferedDistanceTransform(tempImage->mBits, w, h); for (int i = 0; i < aSize; i++) { float dist = tempImage->mBits[i] / 256.0f; dist -= spreadSize; if (dist < 0) tempImage->mBits[i] = 0xFF00; else if (dist < 1.0f) tempImage->mBits[i] = BFClamp((int) ((1.0f - dist) * 255.0f * 256.0f), 0, 0xFF00); else tempImage->mBits[i] = 0; } aRadiusLeft -= spreadSize; } else { SoftBlurInit(imageData, tempImage, isInside); } float radiusOffset = 1.2f; float blurRadius = aRadiusLeft - radiusOffset; if (aRadiusLeft <= 2) blurRadius = 0.5f; if (aRadiusLeft != 0) SoftBlur(tempImage->mBits, w, h, blurRadius, 0); bool doFrontTaper = (!isInside) && (((mContour.GetVal(0) != 0) || (mNoise != 0))); if ((mAntiAliased) && (!mContour.IsDefault()) && (!doFrontTaper)) AntiAliasIndices(tempImage->mBits, w, h); memset(destImageData->mBits, 0, w*h*sizeof(uint32)); float noise = (float) mNoise / 100.0f; float angle = mUseGlobalLight ? (float) layerInfo->mPSDReader->mGlobalAngle : (float) mLocalAngle; angle *= BF_PI / 180.0f; int ofsX = (int) (BFRound(-cos(angle) * (float) mDistance)); int ofsY = (int) (BFRound(sin(angle) * (float) mDistance)); int maxX = std::min(w, w + ofsX); int maxY = std::min(h, h + ofsY); for (int y = std::max(0, ofsY); y < maxY; y++) { for (int x = std::max(0, ofsX); x < maxX; x++) { int i = x+y*w; PackedColor* aDestColor = (PackedColor*) (destImageData->mBits + i); int blurVal = tempImage->mBits[(x-ofsX)+(y-ofsY)*w]; int idx = (blurVal * (CONTOUR_DATA_SIZE - 1) / 255) / 256; BF_ASSERT(idx >= 0); BF_ASSERT(idx < CONTOUR_DATA_SIZE); int gradientIdx = mContourData[idx]; BF_ASSERT(gradientIdx >= 0); BF_ASSERT(gradientIdx < GRADIENT_DATA_SIZE); destImageData->mBits[i] = ((gradientIdx * 255 / (GRADIENT_DATA_SIZE - 1)) << 24) | (mColor & 0x00FFFFFF); //TODO: Apply 'front taper?' if (mNoise > 0) { if (rand() % 5 < 4) { float pixNoise = 256 * noise; if (aDestColor->a > 0) { float randPct = ((Rand() % 10000) / 5000.0f) - 1.0f; aDestColor->a = BFClamp(aDestColor->a + (int) (randPct * pixNoise + 0.5f), 0, 255); } } } if (doFrontTaper) { float minAlpha = std::min(1.0f, (idx / 4095.0f) * 8.4f); aDestColor->a = std::min((int) aDestColor->a, (int) (minAlpha * 255)); } /*if (i % 7 == 0) { aDestColor->a = 255; aDestColor->r = 255; }*/ } } delete tempImage; } int ImageShadowEffect::GetNeededBorderSize() { return (int) mSize + (int) mDistance; } int ImageDropShadowEffect::GetMixType() { return IMAGEMIX_OUTER; } void ImageGlowEffect::Init() { CreateContourAndGradientData(); } /// void ImageGlowEffect::CreateContourAndGradientData() { mContourData = CreateContourDataTable(&mContour, (float) mRange / 100.0f); mGradientData = CreateGradientDataTable(mColorGradient); } int ImageGlowEffect::GetNeededBorderSize() { //TODO: Empirically we only need (mSize / 2) for soft inner glow... return (int) mSize; } void ChokedPixelTransform(ImageData* src, ImageData* dest, float radius, float chokePct, bool invert, bool soften = false) { int w = dest->mWidth; int h = dest->mHeight; uint32* aDest = dest->mBits; ChamferedDistanceTransformInit(src, dest, invert, soften ? (int) radius : 0); ChamferedDistanceTransform(dest->mBits, w, h); int aSize = w*h; int chokePixels = (int) (radius * chokePct + 0.5f); bool fullChoke = chokePixels == radius; uint32* tempBuffer = new uint32[w*h]; uint32* exterior = dest->mBits; uint32* anInterior = tempBuffer; float rad = radius + 0.5f - chokePixels; int inf = (int) (radius + 2) * 256; if (soften) { uint32* in = src->mBits; int iw = src->mWidth; int ih = src->mHeight; int ox = src->mX - dest->mX; int oy = src->mY - dest->mY; for (int y = 0; y < ih; y++) { for (int x = 0; x < iw; x++) { int anAlpha = in[x+y*iw]>>24; int i = (x+ox)+(y+oy)*w; float dist = aDest[i] / 256.0f; dist -= chokePixels + 0.001f; if (dist < 0) anInterior[i] = 0; else if (dist < 1.0f) anInterior[i] = (int) (dist * 256); else anInterior[i] = (int) (dist * 256); if (anAlpha <= 128) exterior[i] = (int) ((128.0f - anAlpha) * (radius * 2 + 2) + 0.5f); else exterior[i] = 0; } } inf = (int) (128.0f * (radius * 2 + 2) + 0.5f); for (int x = 0; x < w; x++) { for (int y = 0; y < oy; y++) exterior[x+y*w] = inf; for (int y = oy+ih; y < h; y++) exterior[x+y*w] = inf; } for (int y = oy; y < oy+ih; y++) { for (int x = 0; x < ox; x++) exterior[x+y*w] = inf; for (int x = ox+iw; x < w; x++) exterior[x+y*w] = inf; } } else { for (int i = 0; i < aSize; i++) { float dist = aDest[i] / 256.0f; dist -= chokePixels + 0.001f; if (dist < 0) { exterior[i] = inf; anInterior[i] = 0; } else if (dist < 1.0f) { exterior[i] = BFClamp((int) ((1.0f - dist) * 256.0f), 0, 0xFF); anInterior[i] = (int) (dist * 256); } else { exterior[i] = 0; anInterior[i] = (int) (dist * 256); } } } ChamferedDistanceTransform(exterior, w, h); float interiorDiv = (rad + 0.5f); float interiorOffset = 0; float exteriorOffset = -255; float exteriorDivide = (radius + 1.0f - chokePixels); if (soften) { exteriorOffset = 0; } if (fullChoke) { for (int i = 0; i < aSize; i++) aDest[i] = std::min((uint32)0xFF00, exterior[i] * 256); } else { for (int i = 0; i < aSize; i++) { uint32 alphaVal = (uint32) (std::max(0.0f, (int) exterior[i] + exteriorOffset ) * 256 / exteriorDivide); alphaVal = std::min((uint32) 0xFF00, (uint32) (alphaVal * 0.5f + 0x7F00)); uint32 alphaVal2 = (uint32) (std::max(0.0f, (int) anInterior[i] + interiorOffset) * 256 / interiorDiv); if (alphaVal <= 0x7F80) alphaVal = (uint32) std::min((uint32) 0xFF00, (uint32) std::max(0.0f, (0xFF00 - (int) alphaVal2) * 0.5f)); aDest[i] = alphaVal; BF_ASSERT(alphaVal <= 0xFF00); } } delete [] tempBuffer; } void ImageOuterGlowEffect::Apply(PSDLayerInfo* layerInfo, ImageData* imageData, ImageData* destImageData) { int sw = imageData->mWidth; int sh = imageData->mHeight; int w = destImageData->mWidth; int h = destImageData->mHeight; ImageData* newImage = destImageData; float radius = (float) mSize; int aSize = w*h; float spread = (float) mSpread / 100.0f; float spreadSize = (float) (int) (mSize * spread + 0.5f); int distTransSize = 0; if (mTechnique == 'PrBL') { ChokedPixelTransform(imageData, newImage, radius, spread, false); } else if (mTechnique == 'SfBL') { radius = std::max(radius, 2.0f); // Radius of '1' gets bumped up to 2 float aRadiusLeft = radius; if (spreadSize != 0) { // Do distance transform first to "grow" area float distLenOffset = spreadSize / 20.0f; float distLen = spreadSize - 1.0f + distLenOffset; ChamferedDistanceTransformInit(imageData, newImage, false); ChamferedDistanceTransform(newImage->mBits, w, h); for (int i = 0; i < aSize; i++) { float dist = newImage->mBits[i] / 256.0f; dist -= spreadSize; if (dist < 0) newImage->mBits[i] = 0xFF00; else if (dist < 1.0f) newImage->mBits[i] = BFClamp((int) ((1.0f - dist) * 255.0f * 256.0f), 0, 0xFF00); else newImage->mBits[i] = 0; } aRadiusLeft -= spreadSize; } else { SoftBlurInit(imageData, newImage, false); } float radiusOffset = 1.2f; float blurRadius = aRadiusLeft - radiusOffset; if (aRadiusLeft <= 2) blurRadius = 0.5f; if ((aRadiusLeft != 0) && (mSize != 0)) SoftBlur(newImage->mBits, w, h, blurRadius, 0); } //OutputDebugStrF("Contour Time: %d\r\n", timeGetTime() - tickStart); bool doFrontTaper = ((mColorGradient[3].GetVal((1.0f - mContour.GetVal(0) / 255.0f) * mColorGradient[3].mXSize) != 0) || (mNoise != 0)); if ((mAntiAliased) && (!mContour.IsDefault()) && (!doFrontTaper)) AntiAliasIndices(newImage->mBits, w, h); float noise = (float) mNoise / 100.0f; float jitter = (float) mJitter / 100.0f; for (int i = 0; i < aSize; i++) { PackedColor* srcColor = (PackedColor*) (imageData->mBits + i); PackedColor* aDestColor = (PackedColor*) (newImage->mBits + i); int blurVal = newImage->mBits[i]; int idx = (blurVal * (CONTOUR_DATA_SIZE - 1) / 255) / 256; BF_ASSERT(idx >= 0); BF_ASSERT(idx < CONTOUR_DATA_SIZE); int gradientIdx = mContourData[idx]; if ((jitter > 0) && (mHasGradient)) { if (rand() % 5 < 4) { float idxJitter = GRADIENT_DATA_SIZE * jitter; if (idx != 0) { float randPct = ((Rand() % 10000) / 5000.0f) - 1.0f; gradientIdx = BFClamp(gradientIdx + (int) (randPct * idxJitter + 0.5f), 0, CONTOUR_DATA_SIZE - 1); } } } BF_ASSERT(gradientIdx >= 0); BF_ASSERT(gradientIdx < GRADIENT_DATA_SIZE); newImage->mBits[i] = mGradientData[gradientIdx]; if (mNoise > 0) { if (rand() % 5 < 4) { float pixNoise = 256 * noise; if (aDestColor->a > 0) { float randPct = ((Rand() % 10000) / 5000.0f) - 1.0f; aDestColor->a = BFClamp(aDestColor->a + (int) (randPct * pixNoise + 0.5f), 0, 255); } } } if (doFrontTaper) { float minAlpha = std::min(1.0f, (idx / 4095.0f) * 8.4f); aDestColor->a = std::min(aDestColor->a, (uint8) (minAlpha * 255)); } } } int ImageOuterGlowEffect::GetMixType() { return IMAGEMIX_OUTER; } /// void ImageInnerGlowEffect::Apply(PSDLayerInfo* layerInfo, ImageData* imageData, ImageData* destImageData) { int sw = destImageData->mWidth; int sh = destImageData->mHeight; int w = sw; int h = sh; ImageData* newImage = destImageData; float radius = (float) mSize; int aSize = w*h; float choke = (float) mChoke / 100.0f; float chokePixels = (float) (int) (choke * mSize + 0.5f); bool fullChoke = chokePixels == mSize; float rad = radius + 0.5f - chokePixels; float inf = (radius + 1.5f) * 256; if (mTechnique == 'PrBL') { ChokedPixelTransform(imageData, newImage, radius, choke, true); } else if (mTechnique == 'SfBL') { // Soft blur float aRadiusLeft = radius; if (chokePixels > 0) { aRadiusLeft -= chokePixels; ChamferedDistanceTransformInit(imageData, newImage, true); ChamferedDistanceTransform(newImage->mBits, w, h); for (int i = 0; i < aSize; i++) { float dist = newImage->mBits[i] / 256.0f; dist -= chokePixels + 0.001f; /*if (((int) (imageData->mBits[i] >> 24) == 255) && (dist >= 0)) newImage->mBits[i] = std::max(0, 256 - (int) (dist * 256)) * 256; else newImage->mBits[i] = 255*256; */ if (dist < 0) newImage->mBits[i] = 0xFF00; else if (dist < 1.0f) newImage->mBits[i] = BFClamp((int) ((1.0f - dist) * 255.0f * 256.0f), 0, 0xFF00); else newImage->mBits[i] = 0; } } else { SoftBlurInit(imageData, newImage, true); } SoftBlur(newImage->mBits, w, h, GetSoftBlurRadius(radius, aRadiusLeft), 0xFF00); } //OutputDebugStrF("Contour Time: %d\r\n", timeGetTime() - tickStart); if ((mAntiAliased) && (!mContour.IsDefault())) AntiAliasIndices(newImage->mBits, w, h); float noise = (float) mNoise / 100.0f; float jitter = (float) mJitter / 100.0f; for (int i = 0; i < aSize; i++) { PackedColor* aDestColor = (PackedColor*) (newImage->mBits + i); int blurVal = newImage->mBits[i]; int idx = (blurVal * (CONTOUR_DATA_SIZE - 1) / 255) / 256; BF_ASSERT(idx >= 0); BF_ASSERT(idx < CONTOUR_DATA_SIZE); int gradientIdx = mContourData[idx]; if ((jitter > 0) && (mHasGradient)) { if (rand() % 5 < 4) { float idxJitter = GRADIENT_DATA_SIZE * jitter; if (idx != 0) { float randPct = ((Rand() % 10000) / 5000.0f) - 1.0f; gradientIdx = BFClamp(gradientIdx + (int) (randPct * idxJitter + 0.5f), 0, CONTOUR_DATA_SIZE - 1); } } } BF_ASSERT(gradientIdx >= 0); BF_ASSERT(gradientIdx < GRADIENT_DATA_SIZE); newImage->mBits[i] = mGradientData[gradientIdx]; if (mIsCenter) aDestColor->a = std::max(0, 255 - aDestColor->a); if (mNoise > 0) { if (rand() % 5 < 4) { float pixNoise = 256 * noise; if (aDestColor->a > 0) { float randPct = ((Rand() % 10000) / 5000.0f) - 1.0f; aDestColor->a = BFClamp(aDestColor->a + (int) (randPct * pixNoise + 0.5f), 0, 255); } } } } } /// ImageBevelEffect::ImageBevelEffect() { mGlossContourData = NULL; } ImageBevelEffect::~ImageBevelEffect() { delete mGlossContourData; } void ImageBevelEffect::Init() { mGlossContourData = CreateContourDataTable(&mGlossContour); if (mUseContour) { float aRange = (float) (mBevelContourRange / 100.0f); mBevelContourData = new int32[CONTOUR_DATA_SIZE]; int minVal = 0; for (int i = 0; i < CONTOUR_DATA_SIZE; i++) { float aX = i / (float) (CONTOUR_DATA_SIZE - 1); aX = (aX - (1.0f - aRange)) / aRange; float yVal = mBevelContour.GetVal(aX * 255.0f) / 255.0f; int aVal = (int) (yVal * 0xFF00 + 0.5f); minVal = std::min(minVal, aVal); mBevelContourData[i] = aVal; } for (int i = 0; i < CONTOUR_DATA_SIZE; i++) mBevelContourData[i] -= minVal; } } void ImageBevelEffect::Apply(PSDLayerInfo* layerInfo, ImageData* imageData, ImageData* destImageData) { } int ImageBevelEffect::GetMixType() { if (mStyle == 'OtrB') // Outer bevel return IMAGEMIX_OUTER; if (mStyle == 'Embs') // Emboss return IMAGEMIX_OVER; if (mStyle == 'PlEb') // Pillow emboss return IMAGEMIX_OVER; return IMAGEMIX_INNER; } void ImageBevelEffect::Apply(int pass, int style, PSDLayerInfo* layerInfo, ImageData* imageData, ImageData* hiliteImage, ImageData* shadowImage) { int w = hiliteImage->mWidth; int h = hiliteImage->mHeight; int aSize = w*h; uint32* normalMap = new uint32[aSize]; PSDLayerInfo* aLayerInfo = layerInfo; ImageData* anImageData = imageData; ImageData* newImage = hiliteImage; float aDepth = (float) mDepth / 100.0f; float aRadius = (float) mSize; float angle = mUseGlobalLight ? (float) aLayerInfo->mPSDReader->mGlobalAngle : (float) mLocalAngle; float altitude = mUseGlobalLight ? (float) aLayerInfo->mPSDReader->mGlobalAltitude : (float) mLocalAltitude; float lightAngle = angle * BF_PI / 180.0f; float lightElevation = altitude * BF_PI / 180.0f; bool doFlip = !mDirectionUp; if (pass == 1) doFlip = !doFlip; if (doFlip) lightAngle = lightAngle + BF_PI; Vector3 shadowVec(cosf((float) lightElevation) * -cosf(lightAngle), cosf(lightElevation) * sinf(lightAngle), sinf(lightElevation)); bool doOuterTaper = (style == 'OtrB') || (style == 'Embs') || (style == 'PlEb'); float blurRadius = aRadius; if ((style == 'Embs') || (style == 'PlEb')) blurRadius = (float) (int) (blurRadius / 2 + 0.75f); if (mTechnique == 'SfBL') // Technique:Smooth { SoftBlurInit(anImageData, newImage, false); SoftBlur(newImage->mBits, w, h, blurRadius - 1.0f, 0); for (int i = 0; i < aSize; i++) { uint32 aVal = newImage->mBits[i]; newImage->mBits[i] = std::min(0xFF00, (int) (aVal + 256 / blurRadius)); } } else { ChokedPixelTransform(anImageData, newImage, blurRadius, 0, true, mTechnique == 'Slmt'); for (int i = 0; i < aSize; i++) { uint32 aVal = newImage->mBits[i]; newImage->mBits[i] = 0xFF00 - aVal; } } if (mUseContour) { for (int i = 0; i < aSize; i++) { uint32 aVal = newImage->mBits[i]; int idx = (aVal * (CONTOUR_DATA_SIZE - 1) / 255) / 256; newImage->mBits[i] = mBevelContourData[idx]; } } if (mUseTexture) { PSDPattern* pattern = aLayerInfo->mPSDReader->mPSDPatternMap[mTexture.mPatternName]; int32 texMultiply = (int32) (0x100 * mTextureDepth / 100.0f); int32 texOffset = texMultiply * 255; if (!mTextureInvert) texMultiply = -texMultiply; int mipLevel = 0; float dU = 1.0f / (float) (mTexture.mScale / 100.0f); float dV = dU; float virtPW = (float) pattern->mWidth; float virtPH = (float) pattern->mHeight; while (dU > 1.0001f) { // Select next mip level mipLevel++; PSDPattern* aMip = pattern->GetNextMipLevel(); dU /= 2.0f; dV /= 2.0f; virtPW /= 2.0f; virtPH /= 2.0f; pattern = aMip; } int pw = pattern->mWidth; int ph = pattern->mHeight; float startU = (float) (newImage->mX - (int) BFRound((float) mTexture.mPhaseX)) * dU; while (startU < 0) startU += virtPW; float startV = (float) (newImage->mY - (int) BFRound((float) mTexture.mPhaseY)) * dV; if (startV < 0) startV += virtPH; float aU = startU; float aV = startV; for (int y = 0; y < h; y++) { aU = startU; uint32* aBits = newImage->mBits + (y*w); for (int x = 0; x < w; x++) { int u1 = ((int) aU) % pw; int v1 = ((int) aV) % ph; float ua = aU - (int) aU; float va = aV - (int) aV; int u2 = (u1 + 1) % pw; int v2 = (v1 + 1) % ph; int intensity = (int) (( ((float) pattern->mIntensityBits[u1+v1*pw] * (1.0f - ua) * (1.0f - va)) + ((float) pattern->mIntensityBits[u2+v1*pw] * ( ua) * (1.0f - va)) + ((float) pattern->mIntensityBits[u1+v2*pw] * (1.0f - ua) * ( va)) + ((float) pattern->mIntensityBits[u2+v2*pw] * ( ua) * ( va)) ) * texMultiply + 0.5f); *aBits = *aBits + intensity + texOffset; aBits++; aU += dU; aU -= (int) (aU / virtPW) * virtPW; } aV += dV; aV -= (int) (aV / virtPH) * virtPH; } } CreateNormalMap(newImage->mBits, normalMap, w, h, 255.0f / blurRadius / aDepth * 0.80f, &shadowVec); // Shadows need to be weightedly differently for 'softening' purposes // This equation fits the proper curve, but I don't really understand why float shadowWeighting = sin(lightElevation) / (1.0f - sin(lightElevation)); if ((mAntiAliased) && (!mGlossContour.IsDefault())) AntiAliasIndices(normalMap, w, h); for (int i = 0; i < aSize; i++) { float dot = (int) (normalMap[i] - 0x100000) / (float) 0xFF00; float zeroPt = shadowVec.mZ; //TODO: Cache gloss contour float curvePos = mGlossContour.GetVal(255.0f * dot) / 255.0f; curvePos -= zeroPt; float curveIdx = 0; if (curvePos > 0) { float hilitePct = curvePos / (1.0f - zeroPt); curvePos = hilitePct; curveIdx = curvePos; } else { float shadowPct = curvePos / zeroPt; curvePos /= zeroPt; } float lightVal = curvePos; lightVal = BFClamp(lightVal, -1.0f, 1.0f); if (lightVal < 0) lightVal *= shadowWeighting; normalMap[i] = std::max((uint32) (0xFF00 * lightVal) + 0x100000, (uint32) 0); } float aSoften = (float) mSoften; if (aSoften > 0) { float d = GetSoftBlurRadius(aSoften) / 2; if (d > 0) { uint32* tempBuffer = newImage->mBits; for (int i = 0; i < 2; i++) { BoxBlur(normalMap, tempBuffer, w, h, d, 0); BoxBlur(tempBuffer, normalMap, h, w, d, 0); } } } for (int i = 0; i < aSize; i++) { float lightVal = (int) (normalMap[i] - 0x100000) / (float) 0xFF00; if (lightVal < 0) lightVal /= shadowWeighting; if (doOuterTaper) { float taperVal = newImage->mBits[i] / 256.0f / 255.0f * 7.85f; if (lightVal > 0) lightVal = std::min(lightVal, taperVal); else lightVal = std::max(lightVal, -taperVal); } if (lightVal > 0) { hiliteImage->mBits[i] = ((int) (BFClamp(lightVal, 0.0f, 1.0f) * 255.0f + 0.5f) << 24) | (mHiliteColor & 0x00FFFFFF); shadowImage->mBits[i] = 0; } else { hiliteImage->mBits[i] = 0; shadowImage->mBits[i] = ((int) (BFClamp(-lightVal, 0.0f, 1.0f) * 255.0f + 0.5f) << 24) | (mShadowColor & 0x00FFFFFF); } } delete [] normalMap; } void ImageBevelEffect::Apply(ImageEffectCtx* ctx) { if (mStyle == 'stro') return; ImageData* hiliteEffectImage = new ImageData(); hiliteEffectImage->CreateNew(ctx->mBlendWidth, ctx->mBlendHeight); hiliteEffectImage->mX = ctx->mBlendX; hiliteEffectImage->mY = ctx->mBlendY; ImageData* shadowEffectImage = new ImageData(); shadowEffectImage->CreateNew(ctx->mBlendWidth, ctx->mBlendHeight); shadowEffectImage->mX = ctx->mBlendX; shadowEffectImage->mY = ctx->mBlendY; // Pillow emboss takes two passes, the others only take one for (int aPass = 0; aPass < 2; aPass++) { bool needsMorePasses = false; Apply(aPass, mStyle, ctx->mLayerInfo, ctx->mLayerImage, hiliteEffectImage, shadowEffectImage); if ((aPass == 0) && ((mStyle == 'Embs') || (mStyle == 'PlEb') || (mStyle == 'InrB') || (mStyle == 'PlEb'))) // Emboss or pillow emboss or inner { BlendImage(ctx->mInnerImage, hiliteEffectImage, 0, 0, (float) mHiliteOpacity / 100.0f, mHiliteMode); BlendImage(ctx->mInnerImage, shadowEffectImage, 0, 0, (float) mShadowOpacity / 100.0f, mShadowMode); needsMorePasses |= (mStyle == 'PlEb'); } if ((mStyle == 'Embs') || (mStyle == 'OtrB') || ((mStyle == 'PlEb') && (aPass == 1))) // Emboss or pillow emboss or outer { BlendImage(ctx->mOuterImage, hiliteEffectImage, 0, 0, (float) mHiliteOpacity / 100.0f, mHiliteMode); BlendImage(ctx->mOuterImage, shadowEffectImage, 0, 0, (float) mShadowOpacity / 100.0f, mShadowMode); } if (!needsMorePasses) break; } delete shadowEffectImage; delete hiliteEffectImage; } int ImageBevelEffect::GetNeededBorderSize() { return (int) mSize; } /// void ImageSatinEffect::Init() { mContourData = CreateContourDataTable(&mContour, 1.0f); } void ImageSatinEffect::Apply(PSDLayerInfo* layerInfo, ImageData* imageData, ImageData* destImageData) { int sw = destImageData->mWidth; int sh = destImageData->mHeight; int w = sw; int h = sh; ImageData* newImage = destImageData; int aSize = w*h; ImageData* tempImage = new ImageData(); tempImage->CreateNew(w, h); SoftBlurInit(imageData, tempImage, false); SoftBlur(tempImage->mBits, w, h, GetSoftBlurRadius((float) mSize), 0); float angle = (float) mAngle * BF_PI / 180.0f; int ofsx = (int) (BFRound(cosf(angle) * (float) mDistance)); int ofsy = (int) (BFRound(-sinf(angle) * (float) mDistance)); uint32* tempData = tempImage->mBits; for (int y = 0; y < h; y++) { for (int x = 0; x < w; x++) { int x1 = BFClamp(x + ofsx, 0, w - 1); int y1 = BFClamp(y + ofsy, 0, h - 1); int idx1 = (tempData[x1+y1*w] * (CONTOUR_DATA_SIZE - 1) / 255) / 256; int val1 = mContourData[idx1] * 255 / (GRADIENT_DATA_SIZE - 1); int x2 = BFClamp(x - ofsx, 0, w - 1); int y2 = BFClamp(y - ofsy, 0, h - 1); int idx2 = (tempData[x2+y2*w] * (CONTOUR_DATA_SIZE - 1) / 255) / 256; int val2 = mContourData[idx2] * 255 / (GRADIENT_DATA_SIZE - 1); uint32 aVal = (uint32) abs((int) val1 - (int) val2); newImage->mBits[x+y*w] = aVal; } } if ((mAntiAliased) && (!mContour.IsDefault())) AntiAliasIndices(newImage->mBits, w, h); if (mInvert) { for (int i = 0; i < aSize; i++) newImage->mBits[i] = ((255 - newImage->mBits[i]) << 24) | (mColor & 0x00FFFFFF); } else { for (int i = 0; i < aSize; i++) newImage->mBits[i] = (newImage->mBits[i] << 24) | (mColor & 0x00FFFFFF); } delete tempImage; } int ImageSatinEffect::GetMixType() // Default:Interior { return IMAGEMIX_INNER; } int ImageSatinEffect::GetNeededBorderSize() { return (int) mSize; } void ImageColorOverlayEffect::Apply(PSDLayerInfo* layerInfo, ImageData* imageData, ImageData* destImageData) { mColorFill.Apply(layerInfo, imageData, destImageData); } /// void ImageColorFill::Apply(PSDLayerInfo* layerInfo, ImageData* imageData, ImageData* destImageData) { int w = destImageData->mWidth; int h = destImageData->mHeight; int aSize = w*h; for (int i = 0; i < aSize; i++) destImageData->mBits[i] = mColor; } ImageGradientFill::ImageGradientFill() { mGradientData = NULL; } ImageGradientFill::~ImageGradientFill() { delete mGradientData; } void ImageGradientFill::Apply(PSDLayerInfo* layerInfo, ImageData* imageData, ImageData* destImageData) { int w = destImageData->mWidth; int h = destImageData->mHeight; int iw = layerInfo->mWidth; int ih = layerInfo->mHeight; int ox = layerInfo->mX - destImageData->mX; int oy = layerInfo->mY - destImageData->mY; if (mGradientData == NULL) { mGradientData = CreateGradientDataTable(mColorGradient); if (mReverse) { for (int i = 0; i < GRADIENT_DATA_SIZE / 2; i++) { int endIdx = (GRADIENT_DATA_SIZE - 1) - i; uint32 swap = mGradientData[i]; mGradientData[i] = mGradientData[endIdx]; mGradientData[endIdx] = swap; } } } int minX = w; int maxX = 0; int minY = h; int maxY = 0; for (int y = 0; y < ih; y++) { uint32* checkBits = imageData->mBits + (y*iw); bool hadPixel = false; for (int x = 0; x < iw; x++) { if ((checkBits[x] >> 24) >= 128) { minX = std::min(minX, x + ox); hadPixel = true; break; } } for (int x = iw - 1; x >= 0; x--) { if ((checkBits[x] >> 24) >= 128) { maxX = std::max(maxX, x + ox); hadPixel = true; break; } } if (hadPixel) { minY = std::min(minY, y + oy); maxY = std::max(maxY, y + oy); } } int contentW = std::max(0, maxX - minX + 1); int contentH = std::max(0, maxY - minY + 1); float angleDeg = (float) mAngle; // There seems to be some inaccuracy with photoshop with angles. // There's a large gap between 180 degrees and 179 degrees than there should be, for example // This is not yet simulated here. Offsets are also slightly effected. float angle = BF_PI * angleDeg / 180.0f; float matA = -cosf(angle); float matB = sinf(angle); float matC = sin(angle); float matD = cos(angle); float xOfs = 0; float yOfs = 0; float gradWidth; float gradHeight; float offsetScale = 1.0f; float sizeBasis; if (mAlignWithLayer) { gradWidth = (float) contentW; gradHeight = (float) contentH; xOfs = -(float)contentW/2 - minX; yOfs = -(float)contentH/2 - minY; } else { gradWidth = (float) layerInfo->mPSDReader->mWidth; gradHeight = (float) layerInfo->mPSDReader->mHeight; xOfs = destImageData->mX - (float) gradWidth/2; yOfs = destImageData->mY - (float) gradHeight/2; } xOfs -= ((float) mOffsetX / 100.0f) * gradWidth; yOfs -= ((float) mOffsetY / 100.0f) * gradHeight; float sizeBasisX = gradWidth / fabs(matA); float sizeBasisY = gradHeight / fabs(matB); sizeBasis = std::min(sizeBasisX, sizeBasisY); float scale = (float) mScale / 100.0f; float gradientScale = (float) (GRADIENT_DATA_SIZE - 1) / sizeBasis / scale * 2; for (int y = 0; y < h; y++) { uint32* aBits = destImageData->mBits + (y*w); for (int x = 0; x < w; x++) { float aX = (float) x + xOfs; float aY = (float) y + yOfs; float xRot = (aX * matA) + (aY * matB); float yRot = (aX * matC) + (aY * matD); int gradientIdx; switch (mStyle) { case 'Lnr ': gradientIdx = BFClamp((int) ((xRot/2) * gradientScale + GRADIENT_DATA_SIZE/2 + 0.5f), 0, GRADIENT_DATA_SIZE - 1); break; case 'Rdl ': { float dist = sqrt(xRot*xRot + yRot * yRot); gradientIdx = (GRADIENT_DATA_SIZE - 1) - BFClamp((int) ((dist) * gradientScale + 0.5f), 0, GRADIENT_DATA_SIZE - 1); } break; case 'Angl': { float ang = (atan2(yRot, xRot) / BF_PI / 2.0f) + 0.5f; gradientIdx = BFClamp((int) ((ang) * (GRADIENT_DATA_SIZE - 1) + 0.5f), 0, GRADIENT_DATA_SIZE - 1); } break; case 'Rflc': gradientIdx = (GRADIENT_DATA_SIZE - 1) - BFClamp((int) (fabs(xRot) * gradientScale + 0.5f), 0, GRADIENT_DATA_SIZE - 1); break; case 'Dmnd': gradientIdx = (GRADIENT_DATA_SIZE - 1) - BFClamp((int) ((fabs(xRot) + fabs(yRot)) * gradientScale + 0.5f), 0, GRADIENT_DATA_SIZE - 1); break; } aBits[x] = mGradientData[gradientIdx]; } } } void ImagePatternFill::Apply(PSDLayerInfo* layerInfo, ImageData* imageData, ImageData* destImageData) { PSDPattern* pattern = layerInfo->mPSDReader->mPSDPatternMap[mPatternName]; int mipLevel = 0; int w = destImageData->mWidth; int h = destImageData->mHeight; float dU = 1.0f / (float) (mScale / 100.0f); float dV = dU; float virtPW = (float) pattern->mWidth; float virtPH = (float) pattern->mHeight; while ((dU > 1.0001f) && (pattern->mWidth >= 4) && (pattern->mHeight >= 4)) { // Select next mip level mipLevel++; PSDPattern* aMip = pattern->GetNextMipLevel(); dU /= 2.0f; dV /= 2.0f; virtPW /= 2.0f; virtPH /= 2.0f; pattern = aMip; } int pw = pattern->mWidth; int ph = pattern->mHeight; float phaseX = (float) mPhaseX; float phaseY = (float) mPhaseY; if (mLinkWithLayer) { phaseX += (float) layerInfo->mRefX; phaseY += (float) layerInfo->mRefY; } float startU = (float) (destImageData->mX - (int) BFRound((float) phaseX)) * dU; while (startU < 0) startU += virtPW; float startV = (float) (destImageData->mY - (int) BFRound((float) phaseY)) * dV; if (startV < 0) startV += virtPH; float aU = startU; float aV = startV; for (int y = 0; y < h; y++) { aU = startU; uint32* aBits = destImageData->mBits + (y*w); for (int x = 0; x < w; x++) { int u1 = ((int) aU) % pw; int v1 = ((int) aV) % ph; float ua = aU - (int) aU; float va = aV - (int) aV; int u2 = (u1 + 1) % pw; int v2 = (v1 + 1) % ph; uint32 aColor1 = pattern->mBits[u1+v1*pw]; uint32 aColor2 = pattern->mBits[u2+v1*pw]; uint32 color3 = pattern->mBits[u1+v2*pw]; uint32 color4 = pattern->mBits[u2+v2*pw]; uint32 a1 = (int) ((1.0f - ua) * (1.0f - va) * 256 + 0.5f); uint32 a2 = (int) (( ua) * (1.0f - va) * 256 + 0.5f); uint32 a3 = (int) ((1.0f - ua) * ( va) * 256 + 0.5f); uint32 a4 = (int) (( ua) * ( va) * 256 + 0.5f); *aBits = (((((aColor1 & 0x00FF00FF) * a1) + ((aColor2 & 0x00FF00FF) * a2) + ((color3 & 0x00FF00FF) * a3) + ((color4 & 0x00FF00FF) * a4)) >> 8) & 0x00FF00FF) | (((((aColor1 >> 8) & 0x00FF00FF) * a1) + (((aColor2 >> 8) & 0x00FF00FF) * a2) + (((color3 >> 8) & 0x00FF00FF) * a3) + (((color4 >> 8) & 0x00FF00FF) * a4)) & 0xFF00FF00); //*aBits = aColor1; aBits++; aU += dU; aU -= (int) (aU / virtPW) * virtPW; } aV += dV; aV -= (int) (aV / virtPH) * virtPH; } } void ImagePatternOverlayEffect::Apply(PSDLayerInfo* layerInfo, ImageData* imageData, ImageData* destImageData) { mPattern.Apply(layerInfo, imageData, destImageData); } void ImageGradientOverlayEffect::Apply(PSDLayerInfo* layerInfo, ImageData* imageData, ImageData* destImageData) { mGradientFill.Apply(layerInfo, imageData, destImageData); } void ImageStrokeEffect::Apply(PSDLayerInfo* layerInfo, ImageData* imageData, ImageData* destImageData) { int w = destImageData->mWidth; int h = destImageData->mHeight; int aSize = w*h; ImageData* tempImage = new ImageData(); tempImage->CreateNew(w, h); tempImage->mX = destImageData->mX; tempImage->mY = destImageData->mY; float aRadius = (float) mSize; float blurRadius = aRadius; if (mPosition == 'CtrF') blurRadius /= 2; uint32* aDest = tempImage->mBits; ChamferedDistanceTransformInit(imageData, tempImage, false, 0); ChamferedDistanceTransform(aDest, w, h); uint32* tempBuffer = destImageData->mBits;//new uint32[w*h]; uint32* exterior = aDest; uint32* anInterior = tempBuffer; float rad = blurRadius; int inf = (int) (blurRadius + 2) * 256; for (int i = 0; i < aSize; i++) { float dist = aDest[i] / 256.0f; dist -= 0.001f; if (dist < 0) { exterior[i] = inf; anInterior[i] = 0; } else if (dist < 1.0f) { exterior[i] = BFClamp((int) ((1.0f - dist) * 256.0f), 0, 0xFF); anInterior[i] = (int) (dist * 256); } else { exterior[i] = 0; anInterior[i] = (int) (dist * 256); } } ChamferedDistanceTransform(exterior, w, h); for (int i = 0; i < aSize; i++) { float distInterior = (anInterior[i] / 256.0f) - rad; float distExterior = (exterior[i] / 256.0f) - rad; float maxDist = std::max(distInterior, distExterior); if (maxDist < 0) aDest[i] = 0xFF000000; else if (maxDist < 1.0f) aDest[i] = ((int) (255.0f * (1.0f - maxDist))) << 24; else aDest[i] = 0; } tempImage->mX = destImageData->mX; tempImage->mY = destImageData->mY; if (mFillType == 'Ptrn') mPatternFill.Apply(layerInfo, imageData, destImageData); else if (mFillType == 'GrFl') mGradientFill.Apply(layerInfo, imageData, destImageData); else mColorFill.Apply(layerInfo, imageData, destImageData); for (int i = 0; i < aSize; i++) { uint32 srcAlpha = tempImage->mBits[i] >> 24; uint32 destAlpha = destImageData->mBits[i] >> 24; destImageData->mBits[i] = ((int) (srcAlpha * destAlpha / 255 + 0.5f) << 24) | (destImageData->mBits[i] & 0x00FFFFFF); } delete tempImage; } void ImageStrokeEffect::Apply(ImageEffectCtx* ctx) { // Try to find a bevel effect with a 'Stroke Emboss' style ImageBevelEffect* bevelEffect = NULL; for (int effectIdx = 0; effectIdx < (int) ctx->mLayerInfo->mImageEffects->mImageEffectVector.size(); effectIdx++) { BaseImageEffect* anEffect = ctx->mLayerInfo->mImageEffects->mImageEffectVector[effectIdx]; bevelEffect = dynamic_cast(anEffect); if ((bevelEffect != NULL) && (bevelEffect->mStyle == 'stro')) break; bevelEffect = NULL; } if ((mOpacity == 100.0) && (bevelEffect == NULL)) { BaseImageEffect::Apply(ctx); return; } ImageData* effectImage = new ImageData(); effectImage->CreateNew(ctx->mBlendWidth, ctx->mBlendHeight); effectImage->mX = ctx->mBlendX; effectImage->mY = ctx->mBlendY; Apply(ctx->mLayerInfo, ctx->mLayerImage, effectImage); float opacity = (float) mOpacity / 100.0f; ImageData* mixImage = CreateResizedImageUnion(ctx->mOrigImage, effectImage->mX, effectImage->mY, effectImage->mWidth, effectImage->mHeight); BlendImage(mixImage, effectImage, effectImage->mX - mixImage->mX, effectImage->mY - mixImage->mY, opacity, mBlendMode, true); if (bevelEffect != NULL) { ImageData* hiliteEffectImage = new ImageData(); hiliteEffectImage->CreateNew(ctx->mBlendWidth, ctx->mBlendHeight); hiliteEffectImage->mX = ctx->mBlendX; hiliteEffectImage->mY = ctx->mBlendY; ImageData* shadowEffectImage = new ImageData(); shadowEffectImage->CreateNew(ctx->mBlendWidth, ctx->mBlendHeight); shadowEffectImage->mX = ctx->mBlendX; shadowEffectImage->mY = ctx->mBlendY; int aStyle = 'Embs'; if (mPosition == 'OutF') aStyle = 'OtrB'; else if (mPosition == 'InsF') aStyle = 'InrB'; bevelEffect->Apply(0, aStyle, ctx->mLayerInfo, ctx->mLayerImage, hiliteEffectImage, shadowEffectImage); BlendImage(mixImage, hiliteEffectImage, hiliteEffectImage->mX - mixImage->mX, hiliteEffectImage->mY - mixImage->mY, (float) bevelEffect->mHiliteOpacity / 100.0f, bevelEffect->mHiliteMode); BlendImage(mixImage, shadowEffectImage, shadowEffectImage->mX - mixImage->mX, shadowEffectImage->mY - mixImage->mY, (float) bevelEffect->mShadowOpacity / 100.0f, bevelEffect->mShadowMode); delete shadowEffectImage; delete hiliteEffectImage; } int mixType = GetMixType(); if ((mixType == IMAGEMIX_INNER) || (mixType == IMAGEMIX_OVER)) BlendImagesTogether(ctx->mInnerImage, mixImage, effectImage); if ((mixType == IMAGEMIX_OUTER) || (mixType == IMAGEMIX_OVER)) BlendImagesTogether(ctx->mOuterImage, mixImage, effectImage); delete mixImage; delete effectImage; } int ImageStrokeEffect::GetMixType() // Default:Interior { if (mPosition == 'OutF') // Outside return IMAGEMIX_OUTER; if (mPosition == 'InsF') // Inside return IMAGEMIX_INNER; if (mPosition == 'CtrF') // Inside return IMAGEMIX_OVER; return IMAGEMIX_INNER; } int ImageStrokeEffect::GetNeededBorderSize() { return (int)(mSize + 2.5); } bool ImageStrokeEffect::NeedsOrigBits(ImageEffects* effects) { ImageBevelEffect* bevelEffect = NULL; for (int effectIdx = 0; effectIdx < (int) effects->mImageEffectVector.size(); effectIdx++) { BaseImageEffect* anEffect = effects->mImageEffectVector[effectIdx]; bevelEffect = dynamic_cast(anEffect); if ((bevelEffect != NULL) && (bevelEffect->mStyle == 'stro')) return true; } return mOpacity != 100.0; }