Skip to content

Commit

Permalink
WebP画像のデコードに対応
Browse files Browse the repository at this point in the history
  • Loading branch information
upsilon committed Jun 2, 2024
1 parent 80c8dd9 commit bd72b10
Show file tree
Hide file tree
Showing 6 changed files with 162 additions and 0 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
- 現時点ではメインアカウント以外のタブ設定は次回起動時に保持されません
* NEW: Twemoji 15.1.0 に対応しました
- Unicode 15.1 で追加された絵文字が表示されるようになります
* NEW: WebP画像の表示に対応しました
- プロフィール画像やサムネイル画像にWebPが使われている場合も表示が可能になります
- 「WebP画像拡張機能」がインストールされている環境でのみ動作します
* CHG: 設定画面でのアカウント一覧の表示形式を変更
* CHG: 新規アカウント追加時のダイアログの構成を変更
* CHG: 新規タブの初回に読み込まれた発言を既読状態にする(起動時の初回の読み込みと同じ動作となる)
Expand Down
1 change: 1 addition & 0 deletions OpenTween.Tests/OpenTween.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="altcover" Version="8.6.95" />
<PackageReference Include="Microsoft.Windows.SDK.Contracts" Version="10.0.19041.2" />
<PackageReference Include="Moq" Version="4.20.70" />
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.507">
<PrivateAssets>all</PrivateAssets>
Expand Down
Binary file added OpenTween.Tests/Resources/re1.webp
Binary file not shown.
66 changes: 66 additions & 0 deletions OpenTween.Tests/WebpDecoderTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// OpenTween - Client of Twitter
// Copyright (c) 2024 kim_upsilon (@kim_upsilon) <https://upsilo.net/~upsilon/>
// All rights reserved.
//
// This file is part of OpenTween.
//
// 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 3 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, see <http://www.gnu.org/licenses/>, or write to
// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
// Boston, MA 02110-1301, USA.

using System.Drawing.Imaging;
using System.IO;
using System.Threading.Tasks;
using Xunit;

namespace OpenTween
{
public class WebpDecoderTest
{
[Fact]
public async Task IsWebpImage_PNGTest()
{
using var imgStream = File.OpenRead("Resources/re1.png");
using var memstream = new MemoryStream();
await imgStream.CopyToAsync(memstream);
memstream.TryGetBuffer(out var buffer);

Assert.False(WebpDecoder.IsWebpImage(buffer));
}

[Fact]
public async Task IsWebpImage_WebPTest()
{
using var imgStream = File.OpenRead("Resources/re1.webp");
using var memstream = new MemoryStream();
await imgStream.CopyToAsync(memstream);
memstream.TryGetBuffer(out var buffer);

Assert.True(WebpDecoder.IsWebpImage(buffer));
}

[Fact]
public async Task ConvertFromWebp_Test()
{
using var imgStream = File.OpenRead("Resources/re1.webp");
using var memstream = new MemoryStream();
await imgStream.CopyToAsync(memstream);
memstream.TryGetBuffer(out var buffer);

var converted = await WebpDecoder.ConvertFromWebp(buffer);
using var memoryImage = new MemoryImage(converted);
Assert.Equal(ImageFormat.Png, memoryImage.ImageFormat);
}
}
}
6 changes: 6 additions & 0 deletions OpenTween/MemoryImage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,12 @@ await stream.CopyToAsync(memstream)
var ret = memstream.TryGetBuffer(out var buffer);
Debug.Assert(ret, "TryGetBuffer() == true");

if (WebpDecoder.IsWebpImage(buffer))
{
var transcoded = await WebpDecoder.ConvertFromWebp(buffer);
return new(transcoded);
}

return new(buffer);
}

Expand Down
86 changes: 86 additions & 0 deletions OpenTween/WebpDecoder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// OpenTween - Client of Twitter
// Copyright (c) 2024 kim_upsilon (@kim_upsilon) <https://upsilo.net/~upsilon/>
// All rights reserved.
//
// This file is part of OpenTween.
//
// 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 3 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, see <http://www.gnu.org/licenses/>, or write to
// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
// Boston, MA 02110-1301, USA.

#nullable enable

using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Windows.Graphics.Imaging;

namespace OpenTween
{
public class WebpDecoder
{
// reference: https://mimesniff.spec.whatwg.org/#matching-an-image-type-pattern
private static readonly byte[] PatternMask =
{
0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
};

private static readonly byte[] BytePattern =
{
0x52, 0x49, 0x46, 0x46, 0x00, 0x00, 0x00, 0x00,
0x57, 0x45, 0x42, 0x50, 0x56, 0x50,
};

public static bool IsWebpImage(ArraySegment<byte> buffer)
{
return buffer.WithIndex().Take(PatternMask.Length)
.All(x => (x.Value & PatternMask[x.Index]) == BytePattern[x.Index]);
}

public static async Task<ArraySegment<byte>> ConvertFromWebp(ArraySegment<byte> inputBuffer)
{
const uint WINCODEC_ERR_COMPONENTINITIALIZEFAILURE = 0x88982F8B;

try
{
using var inputStream = new MemoryStream(inputBuffer.Array, inputBuffer.Offset, inputBuffer.Count, writable: false, publiclyVisible: true);
using var raInStream = inputStream.AsRandomAccessStream();
var decoder = await BitmapDecoder.CreateAsync(raInStream);
using var softwareBitmap = await decoder.GetSoftwareBitmapAsync();

using var outputStream = new MemoryStream();
using var raOutStream = outputStream.AsRandomAccessStream();

var encoderId = BitmapEncoder.PngEncoderId;
var encoder = await BitmapEncoder.CreateAsync(encoderId, raOutStream);
encoder.SetSoftwareBitmap(softwareBitmap);
await encoder.FlushAsync();

var ret = outputStream.TryGetBuffer(out var outBuffer);
Debug.Assert(ret, "TryGetBuffer() == true");

return outBuffer;
}
catch (Exception ex)
when (unchecked((uint)ex.HResult) == WINCODEC_ERR_COMPONENTINITIALIZEFAILURE)
{
// 「WebP画像拡張機能」がインストールされていない環境ではエラーになる
throw new InvalidImageException($"WebP codec initialization error (HRESULT: 0x{WINCODEC_ERR_COMPONENTINITIALIZEFAILURE:X})", ex);
}
}
}
}

0 comments on commit bd72b10

Please sign in to comment.