From ea79951788b45bc31286c6ced85bc6608ae625d4 Mon Sep 17 00:00:00 2001 From: Niels Martin Hansen Date: Thu, 24 Jul 2008 02:41:53 +0000 Subject: [PATCH] Added \blur tag for real gaussian blur. Blurs the same things as \be, except in a prettier way. It is possible to combine the two, although it'd be mostly pointless. Originally committed to SVN as r2284. --- vsfilter/subtitles/RTS.cpp | 13 ++- vsfilter/subtitles/Rasterizer.cpp | 53 +++++++++--- vsfilter/subtitles/Rasterizer.h | 2 +- vsfilter/subtitles/STS.cpp | 6 +- vsfilter/subtitles/STS.h | 1 + vsfilter/subtitles/SeparableFilter.h | 121 +++++++++++++++++++++++++++ vsfilter/subtitles/subtitles.vcproj | 4 + 7 files changed, 184 insertions(+), 16 deletions(-) create mode 100644 vsfilter/subtitles/SeparableFilter.h diff --git a/vsfilter/subtitles/RTS.cpp b/vsfilter/subtitles/RTS.cpp index c7bd13696..e63dc7d20 100644 --- a/vsfilter/subtitles/RTS.cpp +++ b/vsfilter/subtitles/RTS.cpp @@ -127,11 +127,11 @@ void CWord::Paint(CPoint p, CPoint org) m_fDrawn = true; - if(!Rasterize(p.x&7, p.y&7, m_style.fBlur)) return; + if(!Rasterize(p.x&7, p.y&7, m_style.fBlur, m_style.fGaussianBlur)) return; } else if((m_p.x&7) != (p.x&7) || (m_p.y&7) != (p.y&7)) { - Rasterize(p.x&7, p.y&7, m_style.fBlur); + Rasterize(p.x&7, p.y&7, m_style.fBlur, m_style.fGaussianBlur); } m_p = p; @@ -1421,6 +1421,8 @@ bool CRenderedTextSubtitle::ParseSSATag(CSubtitle* sub, CStringW str, STSStyle& params.Add(cmd.Mid(2)), cmd = cmd.Left(2); else if(!cmd.Find(L"a")) params.Add(cmd.Mid(1)), cmd = cmd.Left(1); + else if(!cmd.Find(L"blur")) + params.Add(cmd.Mid(4)), cmd = cmd.Left(4); else if(!cmd.Find(L"bord")) params.Add(cmd.Mid(4)), cmd = cmd.Left(4); else if(!cmd.Find(L"be")) @@ -1536,6 +1538,13 @@ bool CRenderedTextSubtitle::ParseSSATag(CSubtitle* sub, CStringW str, STSStyle& if(sub->m_scrAlignment < 0) sub->m_scrAlignment = (n > 0 && n < 12) ? ((((n-1)&3)+1)+((n&4)?6:0)+((n&8)?3:0)) : org.scrAlignment; } + else if(cmd == L"blur") + { + double n = CalcAnimation(wcstod(p, NULL), style.fGaussianBlur, fAnimate); + style.fGaussianBlur = !p.IsEmpty() + ? (n < 0 ? 0 : n) + : org.fGaussianBlur; + } else if(cmd == L"bord") { double dst = wcstod(p, NULL); diff --git a/vsfilter/subtitles/Rasterizer.cpp b/vsfilter/subtitles/Rasterizer.cpp index 7329c5ef6..17d957979 100644 --- a/vsfilter/subtitles/Rasterizer.cpp +++ b/vsfilter/subtitles/Rasterizer.cpp @@ -25,6 +25,7 @@ #include #include #include "Rasterizer.h" +#include "SeparableFilter.h" Rasterizer::Rasterizer() : mpPathTypes(NULL), mpPathPoints(NULL), mPathPoints(0), mpOverlayBuffer(NULL) { @@ -679,7 +680,7 @@ void Rasterizer::DeleteOutlines() mOutline.clear(); } -bool Rasterizer::Rasterize(int xsub, int ysub, int fBlur) +bool Rasterizer::Rasterize(int xsub, int ysub, int fBlur, double fGaussianBlur) { _TrashOverlay(); @@ -700,17 +701,25 @@ bool Rasterizer::Rasterize(int xsub, int ysub, int fBlur) mWideBorder = (mWideBorder+7)&~7; - if(!mWideOutline.empty() || fBlur) + if(!mWideOutline.empty() || fBlur || fGaussianBlur > 0) { + int bluradjust = 0; + if (fGaussianBlur > 0) + mWideBorder += (int)(fGaussianBlur*3*8 + 0.5) | 1; + if (fBlur) + mWideBorder += 8; + + mWideBorder = (mWideBorder+7)&~7; + // Expand the buffer a bit when we're blurring, since that can also widen the borders a bit - width += 2*mWideBorder + (fBlur ? 16 : 0); - height += 2*mWideBorder + (fBlur ? 16 : 0); + width += 2*mWideBorder + bluradjust*2; + height += 2*mWideBorder + bluradjust*2; - xsub += mWideBorder + (fBlur ? 8 : 0); - ysub += mWideBorder + (fBlur ? 8 : 0); + xsub += mWideBorder + bluradjust; + ysub += mWideBorder + bluradjust; - mOffsetX -= mWideBorder + (fBlur ? 8 : 0); - mOffsetY -= mWideBorder + (fBlur ? 8 : 0); + mOffsetX -= mWideBorder + bluradjust; + mOffsetY -= mWideBorder + bluradjust; } mOverlayWidth = ((width+7)>>3) + 1; @@ -759,6 +768,28 @@ bool Rasterizer::Rasterize(int xsub, int ysub, int fBlur) } } + // Do some gaussian blur magic + if (fGaussianBlur > 0) + { + GaussianKernel filter(fGaussianBlur); + if (mOverlayWidth >= filter.width && mOverlayHeight >= filter.width) + { + int pitch = mOverlayWidth*2; + + byte *tmp = new byte[pitch*mOverlayHeight]; + if(!tmp) return(false); + + int border = !mWideOutline.empty() ? 1 : 0; + + byte *src = mpOverlayBuffer + border; + + SeparableFilterX<2>(src, tmp, mOverlayWidth, mOverlayHeight, pitch, filter.kernel, filter.width, filter.divisor); + SeparableFilterY<2>(tmp, src, mOverlayWidth, mOverlayHeight, pitch, filter.kernel, filter.width, filter.divisor); + + delete[] tmp; + } + } + // If we're blurring, do a 3x3 box blur // Can't do it on subpictures smaller than 3x3 pixels for (int pass = 0; pass < fBlur; pass++) @@ -870,9 +901,9 @@ static __forceinline DWORD safe_subtract(DWORD a, DWORD b) __m64 ap = _mm_cvtsi32_si64(a); __m64 bp = _mm_cvtsi32_si64(b); __m64 rp = _mm_subs_pu16(ap, bp); - return (DWORD)_mm_cvtsi64_si32(rp); - // Don't need an EMMS because nothing in Draw() depends on FPU - // and we EMMS at the end of Draw(). + DWORD r = (DWORD)_mm_cvtsi64_si32(rp); + _mm_empty(); + return r; } // For CPUID usage in Rasterizer::Draw diff --git a/vsfilter/subtitles/Rasterizer.h b/vsfilter/subtitles/Rasterizer.h index aa46daa48..efe82565d 100644 --- a/vsfilter/subtitles/Rasterizer.h +++ b/vsfilter/subtitles/Rasterizer.h @@ -85,7 +85,7 @@ public: bool ScanConvert(); bool CreateWidenedRegion(int borderX, int borderY); void DeleteOutlines(); - bool Rasterize(int xsub, int ysub, int fBlur); + bool Rasterize(int xsub, int ysub, int fBlur, double fGaussianBlur); CRect Draw(SubPicDesc& spd, CRect& clipRect, byte* pAlphaMask, int xsub, int ysub, const long* switchpts, bool fBody, bool fBorder); }; diff --git a/vsfilter/subtitles/STS.cpp b/vsfilter/subtitles/STS.cpp index 1080c7d77..fbe565670 100644 --- a/vsfilter/subtitles/STS.cpp +++ b/vsfilter/subtitles/STS.cpp @@ -2907,6 +2907,7 @@ void STSStyle::SetDefault() fUnderline = false; fStrikeOut = false; fBlur = 0; + fGaussianBlur = 0; fontShiftX = fontShiftY = fontAngleZ = fontAngleX = fontAngleY = 0; relativeTo = 2; } @@ -2929,6 +2930,7 @@ bool STSStyle::operator == (STSStyle& s) && alpha[2] == s.alpha[2] && alpha[3] == s.alpha[3] && fBlur == s.fBlur + && fGaussianBlur == s.fGaussianBlur && relativeTo == s.relativeTo && IsFontStyleEqual(s)); } @@ -3004,7 +3006,7 @@ CString& operator <<= (CString& style, STSStyle& s) s.colors[0], s.colors[1], s.colors[2], s.colors[3], s.alpha[0], s.alpha[1], s.alpha[2], s.alpha[3], s.charSet, s.fontName, s.fontSize, s.fontScaleX, s.fontScaleY, s.fontSpacing, s.fontWeight, - (int)s.fItalic, (int)s.fUnderline, (int)s.fStrikeOut, s.fBlur, + (int)s.fItalic, (int)s.fUnderline, (int)s.fStrikeOut, s.fBlur, s.fGaussianBlur, s.fontAngleZ, s.fontAngleX, s.fontAngleY, s.relativeTo); @@ -3027,7 +3029,7 @@ STSStyle& operator <<= (STSStyle& s, CString& style) s.fontName = WToT(GetStr(str)); s.fontSize = GetFloat(str); s.fontScaleX = GetFloat(str); s.fontScaleY = GetFloat(str); s.fontSpacing = GetFloat(str); s.fontWeight = GetInt(str); - s.fItalic = !!GetInt(str); s.fUnderline = !!GetInt(str); s.fStrikeOut = !!GetInt(str); s.fBlur = GetInt(str); + s.fItalic = !!GetInt(str); s.fUnderline = !!GetInt(str); s.fStrikeOut = !!GetInt(str); s.fBlur = GetInt(str); s.fGaussianBlur = GetFloat(str); s.fontAngleZ = GetFloat(str); s.fontAngleX = GetFloat(str); s.fontAngleY = GetFloat(str); s.relativeTo = GetInt(str); } diff --git a/vsfilter/subtitles/STS.h b/vsfilter/subtitles/STS.h index ee513f58d..50fa1986b 100644 --- a/vsfilter/subtitles/STS.h +++ b/vsfilter/subtitles/STS.h @@ -50,6 +50,7 @@ public: bool fUnderline; bool fStrikeOut; int fBlur; + double fGaussianBlur; double fontAngleZ, fontAngleX, fontAngleY; double fontShiftX, fontShiftY; int relativeTo; // 0: window, 1: video, 2: undefined (~window) diff --git a/vsfilter/subtitles/SeparableFilter.h b/vsfilter/subtitles/SeparableFilter.h new file mode 100644 index 000000000..d88988b3c --- /dev/null +++ b/vsfilter/subtitles/SeparableFilter.h @@ -0,0 +1,121 @@ +/* + Copyright 2007 Niels Martin Hansen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + + Contact: + E-mail: + IRC: jfs in #aegisub on irc.rizon.net + + */ + +#pragma once + +#ifdef _OPENMP +#include +#endif +#include + + +// Filter an image in horizontal direction with a one-dimensional filter +// PixelWidth is the distance in bytes between pixels +template +void SeparableFilterX(unsigned char *src, unsigned char *dst, int width, int height, ptrdiff_t stride, int *kernel, int kernel_size, int divisor) +{ +#pragma omp parallel for + for (int y = 0; y < height; y++) { + unsigned char *in = src + y*stride; + unsigned char *out = dst + y*stride; + for (int x = 0; x < width; x++) { + int accum = 0; + for (int k = 0; k < kernel_size; k++) { + int xofs = k - kernel_size/2; + if (x+xofs < 0) xofs += width; + if (x+xofs >= width) xofs -= width; + accum += (int)(in[xofs*PixelDist] * kernel[k]); + } + accum /= divisor; + if (accum > 255) accum = 255; + if (accum < 0) accum = 0; + *out = (unsigned char)accum; + in+=PixelDist; + out+=PixelDist; + } + } +} + + +// Filter an image in vertical direction with a one-dimensional filter +// This one templated with PixelWidth since the channel interlacing is horizontal only, +// filtering once vertically will automatically catch all channels. +// (Width must be multiplied by pixel width for that to happen though.) +template +void SeparableFilterY(unsigned char *src, unsigned char *dst, int width, int height, ptrdiff_t stride, int *kernel, int kernel_size, int divisor) +{ +#pragma omp parallel for + width *= PixelDist; + for (int x = 0; x < width; x+=PixelDist) { + unsigned char *in = src + x; + unsigned char *out = dst + x; + for (int y = 0; y < height; y++) { + int accum = 0; + for (int k = 0; k < kernel_size; k++) { + int yofs = k - kernel_size/2; + if (y+yofs < 0) yofs += height; + if (y+yofs >= height) yofs -= height; + accum += (int)(in[yofs*stride] * kernel[k]); + } + accum /= divisor; + if (accum > 255) accum = 255; + if (accum < 0) accum = 0; + *out = (unsigned char)accum; + in += stride; + out += stride; + } + } +} + + +static inline double NormalDist(double sigma, double x) +{ + if (sigma <= 0 && x == 0) return 1; + else if (sigma <= 0) return 0; + else return exp(-(x*x)/(2*sigma*sigma)) / (sigma * sqrt(2*3.1415926535)); +} + + +struct GaussianKernel { + int *kernel; + int width; + int divisor; + inline GaussianKernel(double sigma) + { + width = (int)(sigma*3 + 0.5) | 1; // binary-or with 1 to make sure the number is odd + if (width < 3) width = 3; + kernel = new int[width]; + kernel[width/2] = (int)(NormalDist(sigma, 0) * 255); + divisor = kernel[width/2]; + for (int x = width/2-1; x >= 0; x--) { + int val = (int)(NormalDist(sigma, width/2-x) * 255 + 0.5); + divisor += val*2; + kernel[x] = val; + kernel[width - x - 1] = val; + } + } + inline ~GaussianKernel() + { + delete[] kernel; + } +}; diff --git a/vsfilter/subtitles/subtitles.vcproj b/vsfilter/subtitles/subtitles.vcproj index c0a592bdb..d3bfb85bc 100644 --- a/vsfilter/subtitles/subtitles.vcproj +++ b/vsfilter/subtitles/subtitles.vcproj @@ -240,6 +240,10 @@ RelativePath=".\RTS.h" > + +