/// <summary> /// pic.twitter.com アップロード時に JPEG への変換を回避するための加工を行う /// </summary> /// <remarks> /// pic.twitter.com へのアップロード時に、アルファチャンネルを持たない PNG 画像が /// JPEG 形式に変換され画質が低下する問題を回避します。 /// PNG 以外の画像や、すでにアルファチャンネルを持つ PNG 画像に対しては何もしません。 /// </remarks> /// <returns>加工が行われた場合は true、そうでない場合は false</returns> private bool AddAlphaChannelIfNeeded(Image origImage, out MemoryImage newImage) { newImage = null; // PNG 画像以外に対しては何もしない if (origImage.RawFormat.Guid != ImageFormat.Png.Guid) { return(false); } using (var bitmap = new Bitmap(origImage)) { // アルファ値が 255 以外のピクセルが含まれていた場合は何もしない foreach (var x in Enumerable.Range(0, bitmap.Width)) { foreach (var y in Enumerable.Range(0, bitmap.Height)) { if (bitmap.GetPixel(x, y).A != 255) { return(false); } } } // 左上の 1px だけアルファ値を 254 にする var pixel = bitmap.GetPixel(0, 0); var newPixel = Color.FromArgb(pixel.A - 1, pixel.R, pixel.G, pixel.B); bitmap.SetPixel(0, 0, newPixel); // MemoryImage 作成時に画像はコピーされるため、この後 bitmap は破棄しても問題ない newImage = MemoryImage.CopyFromImage(bitmap); return(true); } }
public override async Task <MemoryImage> LoadThumbnailImageAsync(HttpClient http, CancellationToken cancellationToken) { // 画像中央に描画されるタイル (ピクセル単位ではなくタイル番号を表す) // タイル番号に小数部が含まれているが、これはタイル内の相対的な位置を表すためこのまま保持する var centerTileNum = this.WorldToTilePos(this.Longitude, this.Latitude, this.Zoom); // 画像左上に描画されるタイル var topLeftTileNum = PointF.Add(centerTileNum, new SizeF(-this.ThumbnailSize.Width / 2.0f / TileSize.Width, -this.ThumbnailSize.Height / 2.0f / TileSize.Height)); // タイル番号の小数部をもとに、タイル画像を描画する際のピクセル単位のオフセットを算出する var tileOffset = Size.Round(new SizeF(-TileSize.Width * (topLeftTileNum.X - (int)topLeftTileNum.X), -TileSize.Height * (topLeftTileNum.Y - (int)topLeftTileNum.Y))); // 縦横のタイル枚数 var tileCountX = (int)Math.Ceiling((double)(this.ThumbnailSize.Width + Math.Abs(tileOffset.Width)) / TileSize.Width); var tileCountY = (int)Math.Ceiling((double)(this.ThumbnailSize.Height + Math.Abs(tileOffset.Height)) / TileSize.Height); // 読み込む対象となるタイル画像が 10 枚を越えていたら中断 // ex. 一辺が 512px 以内のサムネイル画像を生成する場合、必要なタイル画像は最大で 9 枚 if (tileCountX * tileCountY > 10) { throw new OperationCanceledException(); } // タイル画像を読み込む var tilesTask = new Task <MemoryImage> [tileCountX, tileCountY]; foreach (var x in Enumerable.Range(0, tileCountX)) { foreach (var y in Enumerable.Range(0, tileCountY)) { var tilePos = Point.Add(Point.Truncate(topLeftTileNum), new Size(x, y)); tilesTask[x, y] = this.LoadTileImageAsync(http, tilePos); } } await Task.WhenAll(tilesTask.Cast <Task <MemoryImage> >()) .ConfigureAwait(false); using (var bitmap = new Bitmap(this.ThumbnailSize.Width, this.ThumbnailSize.Height)) { using (var g = Graphics.FromImage(bitmap)) { g.TranslateTransform(tileOffset.Width, tileOffset.Height); foreach (var x in Enumerable.Range(0, tileCountX)) { foreach (var y in Enumerable.Range(0, tileCountY)) { using (var image = tilesTask[x, y].Result) { g.DrawImage(image.Image, TileSize.Width * x, TileSize.Height * y); } } } } MemoryImage result = null; try { result = MemoryImage.CopyFromImage(bitmap); return(result); } catch { result?.Dispose(); throw; } } }