|
3 | 3 | using System.ComponentModel; |
4 | 4 | using System.Data; |
5 | 5 | using System.Drawing; |
| 6 | +using System.Drawing.Drawing2D; |
6 | 7 | using System.Drawing.Imaging; |
7 | 8 | using System.Linq; |
8 | 9 | using System.Runtime.InteropServices; |
@@ -58,6 +59,12 @@ enum Direction |
58 | 59 | }; |
59 | 60 | Dictionary<int, char> clusterSymbolMap = new Dictionary<int, char>(); |
60 | 61 |
|
| 62 | + // K값에 따라 다른 이미지 로딩 |
| 63 | + private Dictionary<int, Bitmap> kToImageMap = null; |
| 64 | + |
| 65 | + // 기존 저장 모드 |
| 66 | + private string previousMode = null; |
| 67 | + |
61 | 68 | int cnt = 0; |
62 | 69 | double avgxx = 0, avgyy = 0, avgdd1 = 0, avgdd2 = 0; |
63 | 70 |
|
@@ -87,8 +94,21 @@ public SaveKMeansForm(Bitmap original, Bitmap pixelated, int pixelSize, |
87 | 94 | } |
88 | 95 | private void cmbSaveForm_SelectedIndexChanged(object sender, EventArgs e) |
89 | 96 | { |
| 97 | + string currentMode = cmbSaveForm.SelectedItem?.ToString(); |
| 98 | + |
| 99 | + // "콜라주 사진 만들기"가 선택됐고, 이전 모드가 다른 경우 → 이미지 다시 고르게 만듦 |
| 100 | + if (currentMode == "콜라주 사진 만들기" && previousMode != "콜라주 사진 만들기") |
| 101 | + { |
| 102 | + kToImageMap = null; // 이전 모드에서 돌아온 경우이므로 새로 불러오기 허용 |
| 103 | + } |
| 104 | + |
| 105 | + if (currentMode == previousMode) |
| 106 | + return; |
| 107 | + |
| 108 | + previousMode = currentMode; |
90 | 109 | UpdatePreview(); |
91 | 110 | } |
| 111 | + |
92 | 112 | private void UpdatePreview() |
93 | 113 | { |
94 | 114 | string mode = cmbSaveForm.SelectedItem?.ToString(); |
@@ -216,6 +236,115 @@ private void UpdatePreview() |
216 | 236 | picSavePreview.Image = resizedImage; |
217 | 237 | picSavePreview.Invalidate(); |
218 | 238 | } |
| 239 | + else if (mode == "콜라주 사진 만들기") |
| 240 | + { |
| 241 | + if (numberGrid == null || pixelColors == null) |
| 242 | + { |
| 243 | + MessageBox.Show("먼저 픽셀화를 진행하세요."); |
| 244 | + return; |
| 245 | + } |
| 246 | + |
| 247 | + int width = originalImage.Width; |
| 248 | + int height = originalImage.Height; |
| 249 | + int cellSize = pixelSize; |
| 250 | + |
| 251 | + // 사용자로부터 이미지 선택 (1회만) |
| 252 | + if (kToImageMap == null) |
| 253 | + { |
| 254 | + kToImageMap = new Dictionary<int, Bitmap>(); |
| 255 | + for (int i = 1; i <= k; i++) |
| 256 | + { |
| 257 | + using (OpenFileDialog ofd = new OpenFileDialog()) |
| 258 | + { |
| 259 | + ofd.Title = $"K = {i}에 사용할 이미지를 선택하세요"; |
| 260 | + ofd.Filter = "이미지 파일 (*.png;*.jpg;*.jpeg;*.bmp)|*.png;*.jpg;*.jpeg;*.bmp"; |
| 261 | + |
| 262 | + if (ofd.ShowDialog() == DialogResult.OK) |
| 263 | + kToImageMap[i] = new Bitmap(ofd.FileName); |
| 264 | + else |
| 265 | + { |
| 266 | + Bitmap blank = new Bitmap(width, height); |
| 267 | + using (Graphics g = Graphics.FromImage(blank)) g.Clear(Color.White); |
| 268 | + kToImageMap[i] = blank; |
| 269 | + } |
| 270 | + } |
| 271 | + } |
| 272 | + } |
| 273 | + |
| 274 | + // 1. K별 평균 색 계산 |
| 275 | + Dictionary<int, List<Color>> kColorSamples = new Dictionary<int, List<Color>>(); |
| 276 | + for (int y = 0; y < numberGrid.GetLength(0); y++) |
| 277 | + { |
| 278 | + for (int x = 0; x < numberGrid.GetLength(1); x++) |
| 279 | + { |
| 280 | + Point pt = new Point(x, y); |
| 281 | + if (!pixelColors.ContainsKey(pt)) continue; |
| 282 | + |
| 283 | + int kVal = numberGrid[y, x]; |
| 284 | + if (!kColorSamples.ContainsKey(kVal)) kColorSamples[kVal] = new List<Color>(); |
| 285 | + kColorSamples[kVal].Add(pixelColors[pt]); |
| 286 | + } |
| 287 | + } |
| 288 | + |
| 289 | + Dictionary<int, Color> kTargetColor = new Dictionary<int, Color>(); |
| 290 | + foreach (var kv in kColorSamples) |
| 291 | + { |
| 292 | + int kr = 0, kg = 0, kb = 0; |
| 293 | + foreach (var c in kv.Value) |
| 294 | + { |
| 295 | + kr += c.R; kg += c.G; kb += c.B; |
| 296 | + } |
| 297 | + int count = kv.Value.Count; |
| 298 | + kTargetColor[kv.Key] = Color.FromArgb(kr / count, kg / count, kb / count); |
| 299 | + } |
| 300 | + |
| 301 | + // 2. 각 K 이미지 보정 + 리사이즈 |
| 302 | + Dictionary<int, Bitmap> resizedKImage = new Dictionary<int, Bitmap>(); |
| 303 | + foreach (var pair in kToImageMap) |
| 304 | + { |
| 305 | + int clusterId = pair.Key; |
| 306 | + Color target = kTargetColor.ContainsKey(clusterId) ? kTargetColor[clusterId] : Color.Gray; |
| 307 | + |
| 308 | + |
| 309 | + Bitmap adjusted = TintGrayscaleImage(kToImageMap[clusterId], target); |
| 310 | + |
| 311 | + Bitmap resized = new Bitmap(width, height); |
| 312 | + using (Graphics g = Graphics.FromImage(resized)) |
| 313 | + { |
| 314 | + g.InterpolationMode = InterpolationMode.HighQualityBicubic; |
| 315 | + g.DrawImage(adjusted, 0, 0, width, height); |
| 316 | + } |
| 317 | + adjusted.Dispose(); |
| 318 | + resizedKImage[clusterId] = resized; |
| 319 | + } |
| 320 | + |
| 321 | + // 3. 콜라주 구성 |
| 322 | + resizedImage = new Bitmap(width, height); |
| 323 | + for (int y = 0; y < height; y++) |
| 324 | + { |
| 325 | + for (int x = 0; x < width; x++) |
| 326 | + { |
| 327 | + int gx = x / cellSize; |
| 328 | + int gy = y / cellSize; |
| 329 | + |
| 330 | + if (gx < 0 || gx >= numberGrid.GetLength(1) || gy < 0 || gy >= numberGrid.GetLength(0)) continue; |
| 331 | + |
| 332 | + int kCluster = numberGrid[gy, gx]; |
| 333 | + if (resizedKImage.ContainsKey(kCluster)) |
| 334 | + { |
| 335 | + Color c = resizedKImage[kCluster].GetPixel(x, y); |
| 336 | + resizedImage.SetPixel(x, y, c); |
| 337 | + } |
| 338 | + } |
| 339 | + } |
| 340 | + |
| 341 | + picSavePreview.Image = resizedImage; |
| 342 | + picSavePreview.Invalidate(); |
| 343 | + } |
| 344 | + |
| 345 | + |
| 346 | + |
| 347 | + |
219 | 348 | } |
220 | 349 |
|
221 | 350 | private void btnImgSave_Click(object sender, EventArgs e) |
@@ -394,6 +523,51 @@ private Dictionary<int, char> GenerateRandomSymbolMap(int k) |
394 | 523 | return gray; |
395 | 524 | } |
396 | 525 |
|
| 526 | + // 이미지와 목표 색상을 매개변수로 한 다음 비트맵으로 반환 |
| 527 | + public static Bitmap TintGrayscaleImage(Bitmap input, Color tintColor) |
| 528 | + { |
| 529 | + int width = input.Width; |
| 530 | + int height = input.Height; |
| 531 | + |
| 532 | + Bitmap result = new Bitmap(width, height, PixelFormat.Format24bppRgb); |
| 533 | + Rectangle rect = new Rectangle(0, 0, width, height); |
| 534 | + |
| 535 | + BitmapData data = input.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb); |
| 536 | + BitmapData outputData = result.LockBits(rect, ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb); |
| 537 | + |
| 538 | + int stride = data.Stride; |
| 539 | + byte[] buffer = new byte[stride * height]; |
| 540 | + byte[] outBuffer = new byte[stride * height]; |
| 541 | + |
| 542 | + Marshal.Copy(data.Scan0, buffer, 0, buffer.Length); |
| 543 | + input.UnlockBits(data); |
| 544 | + |
| 545 | + for (int y = 0; y < height; y++) |
| 546 | + { |
| 547 | + int row = y * stride; |
| 548 | + for (int x = 0; x < width; x++) |
| 549 | + { |
| 550 | + int idx = row + x * 3; |
| 551 | + byte b = buffer[idx]; |
| 552 | + byte g = buffer[idx + 1]; |
| 553 | + byte r = buffer[idx + 2]; |
| 554 | + |
| 555 | + // 회색조 밝기 (가중 평균) |
| 556 | + int gray = (int)(r * 0.3 + g * 0.59 + b * 0.11); |
| 557 | + |
| 558 | + outBuffer[idx + 2] = (byte)Math.Min(255, (gray * tintColor.R) / 255); |
| 559 | + outBuffer[idx + 1] = (byte)Math.Min(255, (gray * tintColor.G) / 255); |
| 560 | + outBuffer[idx] = (byte)Math.Min(255, (gray * tintColor.B) / 255); |
| 561 | + } |
| 562 | + } |
| 563 | + |
| 564 | + Marshal.Copy(outBuffer, 0, outputData.Scan0, outBuffer.Length); |
| 565 | + result.UnlockBits(outputData); |
| 566 | + |
| 567 | + return result; |
| 568 | + } |
| 569 | + |
397 | 570 |
|
398 | 571 | } |
399 | 572 | } |
| 573 | + |
0 commit comments