Codemaru 1.0.0.0
QR 코드, 모바일 랜딩 페이지, vCard 연락처 저장과 명함 디자인을 한 화면에서 제공하는 디지털 명함 서비스입니다.
로딩중...
검색중...
일치하는것 없음
ImageBackgroundRemover.cs
이 파일의 문서화 페이지로 가기
1using System.IO;
2using System.Windows;
3using System.Windows.Media;
4using System.Windows.Media.Imaging;
5
6namespace Codemaru.Services;
7
16public sealed class ImageBackgroundRemover
17{
26 private const double ClearDistance = 42;
35 private const double SoftDistance = 86;
44 private const int LogoCanvasWidth = 640;
53 private const int LogoCanvasHeight = 400;
62 private const int LogoPadding = 42;
63
104 public string CreateDataUrl(byte[] imageBytes, string contentType, bool removeBackground)
105 {
106 if (contentType.Contains("svg", StringComparison.OrdinalIgnoreCase))
107 {
108 return $"data:{contentType};base64,{Convert.ToBase64String(imageBytes)}";
109 }
110
111 try
112 {
113 var png = NormalizeLogo(imageBytes, removeBackground);
114 return $"data:image/png;base64,{Convert.ToBase64String(png)}";
115 }
116 catch
117 {
118 return $"data:{contentType};base64,{Convert.ToBase64String(imageBytes)}";
119 }
120 }
121
154 private static byte[] NormalizeLogo(byte[] imageBytes, bool removeBackground)
155 {
156 using var source = new MemoryStream(imageBytes);
157 var decoder = BitmapDecoder.Create(source, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad);
158 var frame = decoder.Frames[0];
159 var bitmap = new FormatConvertedBitmap(frame, PixelFormats.Bgra32, null, 0);
160 var width = bitmap.PixelWidth;
161 var height = bitmap.PixelHeight;
162 var stride = width * 4;
163 var pixels = new byte[stride * height];
164
165 bitmap.CopyPixels(pixels, stride, 0);
166
167 if (removeBackground)
168 {
169 RemoveBackgroundPixels(pixels, width, height, stride);
170 }
171
172 var prepared = BitmapSource.Create(width, height, 96, 96, PixelFormats.Bgra32, null, pixels, stride);
173 var bounds = removeBackground
174 ? FindVisibleBounds(pixels, width, height, stride)
175 : new Int32Rect(0, 0, width, height);
176 var cropped = new CroppedBitmap(prepared, bounds);
177 var fitted = FitIntoCanvas(cropped);
178
179 var encoder = new PngBitmapEncoder();
180 encoder.Frames.Add(BitmapFrame.Create(fitted));
181
182 using var target = new MemoryStream();
183 encoder.Save(target);
184 return target.ToArray();
185 }
186
227 private static void RemoveBackgroundPixels(byte[] pixels, int width, int height, int stride)
228 {
229 var background = SampleBackground(pixels, width, height, stride);
230 for (var y = 0; y < height; y++)
231 {
232 for (var x = 0; x < width; x++)
233 {
234 var i = y * stride + x * 4;
235 var distance = ColorDistance(
236 pixels[i + 2],
237 pixels[i + 1],
238 pixels[i],
239 background.R,
240 background.G,
241 background.B);
242
243 if (distance <= ClearDistance)
244 {
245 pixels[i + 3] = 0;
246 }
247 else if (distance < SoftDistance)
248 {
249 var keep = (distance - ClearDistance) / (SoftDistance - ClearDistance);
250 pixels[i + 3] = (byte)Math.Min(pixels[i + 3], Math.Round(255 * keep));
251 }
252 }
253 }
254 }
255
280 private static BitmapSource FitIntoCanvas(BitmapSource source)
281 {
282 var availableWidth = LogoCanvasWidth - LogoPadding * 2;
283 var availableHeight = LogoCanvasHeight - LogoPadding * 2;
284 var scale = Math.Min((double)availableWidth / source.PixelWidth, (double)availableHeight / source.PixelHeight);
285 var targetWidth = Math.Max(1, source.PixelWidth * scale);
286 var targetHeight = Math.Max(1, source.PixelHeight * scale);
287 var left = (LogoCanvasWidth - targetWidth) / 2;
288 var top = (LogoCanvasHeight - targetHeight) / 2;
289
290 var visual = new DrawingVisual();
291 using (var context = visual.RenderOpen())
292 {
293 context.DrawRectangle(Brushes.Transparent, null, new Rect(0, 0, LogoCanvasWidth, LogoCanvasHeight));
294 context.DrawImage(source, new Rect(left, top, targetWidth, targetHeight));
295 }
296
297 var target = new RenderTargetBitmap(LogoCanvasWidth, LogoCanvasHeight, 96, 96, PixelFormats.Pbgra32);
298 target.Render(visual);
299 target.Freeze();
300 return target;
301 }
302
351 private static Int32Rect FindVisibleBounds(byte[] pixels, int width, int height, int stride)
352 {
353 var left = width;
354 var top = height;
355 var right = -1;
356 var bottom = -1;
357
358 for (var y = 0; y < height; y++)
359 {
360 for (var x = 0; x < width; x++)
361 {
362 var alpha = pixels[y * stride + x * 4 + 3];
363 if (alpha <= 12)
364 {
365 continue;
366 }
367
368 left = Math.Min(left, x);
369 top = Math.Min(top, y);
370 right = Math.Max(right, x);
371 bottom = Math.Max(bottom, y);
372 }
373 }
374
375 if (right < left || bottom < top)
376 {
377 return new Int32Rect(0, 0, width, height);
378 }
379
380 return new Int32Rect(left, top, right - left + 1, bottom - top + 1);
381 }
382
431 private static Color SampleBackground(byte[] pixels, int width, int height, int stride)
432 {
433 var points = new[]
434 {
435 (X: 0, Y: 0),
436 (X: width - 1, Y: 0),
437 (X: 0, Y: height - 1),
438 (X: width - 1, Y: height - 1)
439 };
440
441 var r = 0;
442 var g = 0;
443 var b = 0;
444
445 foreach (var point in points)
446 {
447 var i = point.Y * stride + point.X * 4;
448 b += pixels[i];
449 g += pixels[i + 1];
450 r += pixels[i + 2];
451 }
452
453 return Color.FromRgb((byte)(r / points.Length), (byte)(g / points.Length), (byte)(b / points.Length));
454 }
455
520 private static double ColorDistance(byte r1, byte g1, byte b1, byte r2, byte g2, byte b2)
521 {
522 var r = r1 - r2;
523 var g = g1 - g2;
524 var b = b1 - b2;
525 return Math.Sqrt(r * r + g * g + b * b);
526 }
527}
static void RemoveBackgroundPixels(byte[] pixels, int width, int height, int stride)
static double ColorDistance(byte r1, byte g1, byte b1, byte r2, byte g2, byte b2)
static BitmapSource FitIntoCanvas(BitmapSource source)
static byte[] NormalizeLogo(byte[] imageBytes, bool removeBackground)
static Int32Rect FindVisibleBounds(byte[] pixels, int width, int height, int stride)
string CreateDataUrl(byte[] imageBytes, string contentType, bool removeBackground)
static Color SampleBackground(byte[] pixels, int width, int height, int stride)