|
1 | | -// "Pixelate / blur" through the real image engine: the parts become squares of their average colour, a blur that |
2 | | -// leaves none of their detail or opaque black boxes, each part as its own style says, the rest of the picture keeps every pixel, the parts land on the picture as it is seen |
3 | | -// (EXIF orientation), the format stays, and the previews a file carries of the uncovered picture go. |
| 1 | +// "Pixelate / blur" through the real image engine: the parts become squares of their average colour, a blur that leaves |
| 2 | +// none of their detail or opaque black boxes, each part as its own style says, the rest of the picture keeps every |
| 3 | +// pixel, the parts land on the picture as it is seen (EXIF orientation), the format stays, and the previews a file |
| 4 | +// carries of the uncovered picture go. |
4 | 5 |
|
5 | 6 | using Filee.Core.Conversion; |
6 | 7 | using Filee.Core.Presets; |
@@ -110,13 +111,94 @@ public async Task A_black_box_is_opaque_black_and_each_part_takes_its_own_style( |
110 | 111 | for (var y = 20; y < 120; y++) |
111 | 112 | for (var x = 40; x < 160; x++) |
112 | 113 | Assert.Equal((0, 0, 0, 255), (b[Index(x, y)], b[Index(x, y) + 1], b[Index(x, y) + 2], b[Index(x, y) + 3])); |
113 | | - // Just outside the box, as it was; the other part pixelated (two neighbours in one square alike, unlike before). |
| 114 | + // Just outside the box, as it was; the other part pixelated (two neighbours in one square alike, unlike |
| 115 | + // before). |
114 | 116 | Assert.Equal(a[Index(39, 50)], b[Index(39, 50)]); |
115 | 117 | Assert.Equal(a[Index(160, 50)], b[Index(160, 50)]); |
116 | 118 | Assert.Equal(b[Index(250, 50)], b[Index(251, 50)]); |
117 | 119 | Assert.NotEqual((0, 0, 0), (b[Index(250, 50)], b[Index(250, 50) + 1], b[Index(250, 50) + 2])); |
118 | 120 | } |
119 | 121 |
|
| 122 | + [Theory] |
| 123 | + [InlineData("tif")] |
| 124 | + [InlineData("jpg")] |
| 125 | + public async Task A_black_box_on_a_cmyk_picture_is_full_black_ink_and_its_text_is_gone(string extension) |
| 126 | + { |
| 127 | + // Text in black ink only (K), as print files have it. A box made in sRGB and copied in channel by channel came |
| 128 | + // out with C, M, Y and K all 0: white paper. |
| 129 | + var folder = fx.NewFolder(); |
| 130 | + var path = Path.Combine(folder, "print." + extension); |
| 131 | + using (var source = new MagickImage(MagickColors.White, 300, 100)) |
| 132 | + { |
| 133 | + for (var row = 30; row <= 70; row += 20) |
| 134 | + { |
| 135 | + using var line = new MagickImage(MagickColors.Black, 180, 5); |
| 136 | + source.Composite(line, 60, row, CompositeOperator.Copy); |
| 137 | + } |
| 138 | + source.ColorSpace = ColorSpace.CMYK; |
| 139 | + source.Quality = 95; |
| 140 | + source.Write(path); |
| 141 | + } |
| 142 | + var box = Area(0.1, 0.1, 0.8, 0.8); // 30, 10, 240 × 80 |
| 143 | + box.Style = HideStyle.Box; |
| 144 | + |
| 145 | + var output = await RunAsync(path, Hide(HideStyle.Pixelate, 5, box)); |
| 146 | + |
| 147 | + using var after = new MagickImage(output); |
| 148 | + Assert.Equal(ColorSpace.CMYK, after.ColorSpace); |
| 149 | + using var pixels = after.GetPixels(); |
| 150 | + // Inside, away from the box's edge (JPEG blends 8 px blocks across it): no C, M or Y and all K, on the lines of |
| 151 | + // text and between them. The TIFF is exact. |
| 152 | + var tolerance = extension == "jpg" ? 12 : 0; |
| 153 | + foreach (var (x, y) in new[] { (150, 32), (150, 42), (150, 52), (100, 72), (60, 20) }) |
| 154 | + { |
| 155 | + var value = pixels.GetValue(x, y)!; |
| 156 | + Assert.True(value[0] <= tolerance && value[1] <= tolerance && value[2] <= tolerance && value[3] >= 255 - tolerance, |
| 157 | + $"({x}, {y}): {string.Join(",", value)}"); |
| 158 | + } |
| 159 | + // Outside the box, still white paper. |
| 160 | + Assert.True(pixels.GetValue(10, 50)![3] <= tolerance); |
| 161 | + } |
| 162 | + |
| 163 | + [Theory] |
| 164 | + [InlineData(HideStyle.Box)] |
| 165 | + [InlineData(HideStyle.Pixelate)] |
| 166 | + public async Task Extra_channels_of_a_tiff_keep_nothing_of_the_part(HideStyle style) |
| 167 | + { |
| 168 | + // A TIFF with two extra samples beside RGBA (read as meta channels), each its own noise. Pixelate and Box write |
| 169 | + // colours and alpha only: without clearing them, the part stayed in those channels pixel for pixel. |
| 170 | + var folder = fx.NewFolder(); |
| 171 | + var path = Path.Combine(folder, "layers.tif"); |
| 172 | + using (var channels = new MagickImageCollection()) |
| 173 | + { |
| 174 | + for (var seed = 1; seed <= 6; seed++) |
| 175 | + { |
| 176 | + var channel = Noise(300, 200, seed); |
| 177 | + channel.ColorType = ColorType.Grayscale; |
| 178 | + channels.Add(channel); |
| 179 | + } |
| 180 | + using var combined = channels.Combine(ColorSpace.sRGB); |
| 181 | + combined.Write(path, MagickFormat.Tiff); |
| 182 | + } |
| 183 | + var area = Area(0.1, 0.1, 0.5, 0.5); // 30, 20, 150 × 100 |
| 184 | + |
| 185 | + var output = await RunAsync(path, Hide(style, 5, area)); |
| 186 | + |
| 187 | + using var before = new MagickImage(path); |
| 188 | + using var after = new MagickImage(output); |
| 189 | + Assert.Equal([PixelChannel.Meta0, PixelChannel.Meta1], after.Channels.Where(c => c >= PixelChannel.Meta0)); |
| 190 | + using var a = before.GetPixels(); |
| 191 | + using var b = after.GetPixels(); |
| 192 | + int[] extra = [4, 5]; |
| 193 | + for (var y = 20; y < 120; y++) |
| 194 | + for (var x = 30; x < 180; x++) |
| 195 | + foreach (var c in extra) |
| 196 | + Assert.Equal(0, b.GetValue(x, y)![c]); |
| 197 | + // Outside the part, every channel as it was. |
| 198 | + foreach (var (x, y) in new[] { (29, 50), (180, 50), (100, 19), (100, 120), (250, 150) }) |
| 199 | + Assert.Equal(a.GetValue(x, y), b.GetValue(x, y)); |
| 200 | + } |
| 201 | + |
120 | 202 | [Fact] |
121 | 203 | public async Task Blur_leaves_none_of_the_parts_detail_and_keeps_everything_else() |
122 | 204 | { |
|
0 commit comments