[インデックス 13546] ファイルの概要
このコミットは、Go言語の標準ライブラリであるimage/png
パッケージにおける、PNG画像の透明度情報(tRNSチャンク)の取り扱いに関する修正です。具体的には、tRNSチャンクが「非アルファ乗算済み(non-alpha-premultiplied)」であることを明確にし、それに基づいてエンコーダとデコーダの挙動を調整しています。これにより、PNG画像の透明度が正しく処理されるようになります。
コミット
commit 695024b8fa78677362ad2c3d57fa63de7f5fbab4
Author: Nigel Tao <nigeltao@golang.org>
Date: Wed Aug 1 09:20:44 2012 +1000
image/png: tRNS chunk is *non*-alpha-premultiplied.
R=r
CC=golang-dev
https://golang.org/cl/6446062
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/695024b8fa78677362ad2c3d57fa63de7f5fbab4
元コミット内容
image/png: tRNS chunk is *non*-alpha-premultiplied.
このコミットメッセージは、PNGのtRNSチャンクがアルファ乗算されていない(non-alpha-premultiplied)形式であることを明示し、それに対応する修正が行われたことを示しています。
変更の背景
PNG画像フォーマットにおいて、透明度(アルファチャンネル)の表現方法には「アルファ乗算済み(premultiplied alpha)」と「非アルファ乗算済み(non-premultiplied alpha)」の2種類があります。
- 非アルファ乗算済み(Non-premultiplied alpha): 各ピクセルのRGB値が、そのピクセルのアルファ値によって乗算されていない状態です。これは、色情報と透明度情報が独立して保持されていることを意味します。PNGの仕様では、tRNSチャンク(パレットベースの画像やグレースケール、RGB画像における単一の透明色を指定するチャンク)で指定される透明度は、この非アルファ乗算済み形式であるとされています。
- アルファ乗算済み(Premultiplied alpha): 各ピクセルのRGB値が、そのピクセルのアルファ値によって事前に乗算されている状態です。これは、合成処理(ブレンディング)を行う際に計算が効率的になるという利点がありますが、色情報と透明度情報が密接に結合しています。
Goのimage/png
パッケージでは、tRNSチャンクのデータを処理する際に、この「非アルファ乗算済み」という特性が正しく考慮されていなかった可能性があります。その結果、透明度が期待通りに適用されず、画像が正しく表示されない、または意図しない色になるなどの問題が発生していたと考えられます。このコミットは、この誤解釈を修正し、PNGの仕様に厳密に準拠することで、透明度処理の正確性を向上させることを目的としています。
前提知識の解説
PNG (Portable Network Graphics)
PNGは、可逆圧縮を特徴とするビットマップ画像フォーマットです。ウェブ上で広く利用されており、透明度(アルファチャンネル)をサポートしている点が大きな特徴です。PNGファイルは、複数の「チャンク」と呼ばれるデータブロックで構成されており、それぞれが特定の情報(画像ヘッダ、パレット、画像データなど)を格納しています。
tRNSチャンク (Transparency Chunk)
tRNSチャンクは、PNGファイルにおいて透明度情報を定義するために使用されるオプションのチャンクです。その用途は、画像のカラータイプによって異なります。
- パレットベースの画像 (Color Type 3): パレット内の各エントリ(色)に対して、個別のアルファ値を指定します。これにより、パレット内の特定の色を部分的に透明にしたり、完全に透明にしたりすることができます。
- グレースケール画像 (Color Type 0) および RGB画像 (Color Type 2): 単一の透明色を指定します。この色と完全に一致するピクセルは、完全に透明として扱われます。
このコミットの文脈では、特にパレットベースの画像におけるtRNSチャンクの扱いが重要です。
アルファチャンネルと透明度
アルファチャンネルは、画像の各ピクセルの透明度を表現するための情報です。通常、0(完全に透明)から255(完全に不透明)までの値で表されます。
color.RGBA
と color.NRGBA
(Go言語のimage
パッケージ)
Go言語のimage
パッケージには、色を表現するための様々な型が定義されています。
color.RGBA
: 赤(Red)、緑(Green)、青(Blue)、アルファ(Alpha)の各成分を8ビット(0-255)で表現する色型です。この型は、アルファ乗算されていない色を表します。つまり、RGB値はアルファ値とは独立しています。color.NRGBA
: 非アルファ乗算済み(Non-premultiplied RGBA)の色型です。color.RGBA
と同様に各成分を8ビットで表現しますが、この型は特に「非アルファ乗算済み」であることを明示的に示します。image
パッケージ内での内部的な処理において、アルファ乗算済みと非アルファ乗算済みの区別は重要です。
このコミットでは、tRNSチャンクのデータがcolor.NRGBA
として解釈されるべきであるという認識に基づき、コードが修正されています。
技術的詳細
このコミットの核心は、PNGのtRNSチャンクが非アルファ乗算済みであるという仕様をGoのimage/png
パッケージの実装に正しく反映させることです。
image/png
パッケージは、PNGファイルの読み込み(デコード)と書き込み(エンコード)を処理します。
デコード時、reader.go
のparsetRNS
関数は、tRNSチャンクから読み取ったアルファ値をパレットの色に適用します。元のコードでは、パレットの色をcolor.RGBA
として扱い、そのアルファ値を更新していました。しかし、color.RGBA
は非アルファ乗算済みを意図する型であるものの、内部的な処理でアルファ乗算済みとして扱われる可能性がありました。この修正では、明示的にcolor.NRGBA
型を使用することで、tRNSチャンクのアルファ値が非アルファ乗算済みとして正しくパレットに適用されるように変更されています。
エンコード時、writer.go
のwritePLTEAndTRNS
関数は、パレットとtRNSチャンクを書き出します。元のコードでは、パレットの色からRGBとアルファ値を取得する際に、c.RGBA()
メソッドを使用していました。このメソッドは、色を64ビットのアルファ乗算済みRGBA値として返すため、非アルファ乗算済みのtRNSチャンクのデータとしては不適切でした。修正後では、パレットの色をcolor.NRGBAModel.Convert(c).(color.NRGBA)
によって明示的にcolor.NRGBA
に変換し、そのR, G, B, A成分を直接使用することで、非アルファ乗算済みの正しい値をtRNSチャンクに書き込むようにしています。
また、writer.go
では、一時バッファtmp
のサイズが3 * 256
バイトから4 * 256
バイトに拡張されています。これは、パレットの色情報(RGB各3バイト)に加えて、tRNSチャンクのアルファ情報(1バイト)を格納するために必要なスペースを確保するためです。これにより、writePLTEAndTRNS
関数内でパレットの色とtRNSチャンクのアルファ値を効率的に格納し、書き出すことが可能になります。
さらに、スライス操作の簡略化(例: e.header[0:4]
からe.header[:4]
)や、writeIEND
関数での空のバイトスライスe.tmp[0:0]
の代わりにnil
を渡す変更も行われています。これらは機能的な変更ではなく、コードの可読性や慣用的なGoの書き方に合わせた改善です。
コアとなるコードの変更箇所
src/pkg/image/png/reader.go
--- a/src/pkg/image/png/reader.go
+++ b/src/pkg/image/png/reader.go
@@ -226,7 +226,7 @@ func (d *decoder) parsetRNS(length uint32) error {
}
for i := 0; i < n; i++ {
rgba := d.palette[i].(color.RGBA)
- d.palette[i] = color.RGBA{rgba.R, rgba.G, rgba.B, d.tmp[i]}
+ d.palette[i] = color.NRGBA{rgba.R, rgba.G, rgba.B, d.tmp[i]}
}
case cbGA8, cbGA16, cbTCA8, cbTCA16:
return FormatError("tRNS, color type mismatch")
src/pkg/image/png/reader_test.go
--- a/src/pkg/image/png/reader_test.go
+++ b/src/pkg/image/png/reader_test.go
@@ -107,13 +107,18 @@ func sng(w io.WriteCloser, filename string, png image.Image) {
lastAlpha := -1
io.WriteString(w, "PLTE {\n")
for i, c := range cpm {
- r, g, b, a := c.RGBA()
- if a != 0xffff {
+ var r, g, b, a uint8
+ switch c := c.(type) {
+ case color.RGBA:
+ r, g, b, a = c.R, c.G, c.B, 0xff
+ case color.NRGBA:
+ r, g, b, a = c.R, c.G, c.B, c.A
+ default:
+ panic("unknown palette color type")
+ }
+ if a != 0xff {
lastAlpha = i
}
- r >>= 8
- g >>= 8
- b >>= 8
fmt.Fprintf(w, " (%3d,%3d,%3d) # rgb = (0x%02x,0x%02x,0x%02x)\\n", r, g, b, r, g, b)
}
io.WriteString(w, "}\n")
src/pkg/image/png/writer.go
--- a/src/pkg/image/png/writer.go
+++ b/src/pkg/image/png/writer.go
@@ -21,7 +21,7 @@ type encoder struct {
err error
header [8]byte
footer [4]byte
- tmp [3 * 256]byte
+ tmp [4 * 256]byte
}
// Big-endian.
@@ -70,7 +70,7 @@ func (e *encoder) writeChunk(b []byte, name string) {
e.err = UnsupportedError(name + " chunk is too large: " + strconv.Itoa(len(b)))
return
}
- writeUint32(e.header[0:4], n)
+ writeUint32(e.header[:4], n)
e.header[4] = name[0]
e.header[5] = name[1]
e.header[6] = name[2]
@@ -78,9 +78,9 @@ func (e *encoder) writeChunk(b []byte, name string) {
crc := crc32.NewIEEE()
crc.Write(e.header[4:8])
crc.Write(b)
- writeUint32(e.footer[0:4], crc.Sum32())
+ writeUint32(e.footer[:4], crc.Sum32())
- _, e.err = e.w.Write(e.header[0:8])
+ _, e.err = e.w.Write(e.header[:8])
if e.err != nil {
return
}
@@ -88,7 +88,7 @@ func (e *encoder) writeChunk(b []byte, name string) {
if e.err != nil {
return
}
- _, e.err = e.w.Write(e.footer[0:4])
+ _, e.err = e.w.Write(e.footer[:4])
}
func (e *encoder) writeIHDR() {
@@ -122,36 +122,29 @@ func (e *encoder) writeIHDR() {
e.tmp[10] = 0 // default compression method
e.tmp[11] = 0 // default filter method
e.tmp[12] = 0 // non-interlaced
- e.writeChunk(e.tmp[0:13], "IHDR")
+ e.writeChunk(e.tmp[:13], "IHDR")
}
-func (e *encoder) writePLTE(p color.Palette) {
+func (e *encoder) writePLTEAndTRNS(p color.Palette) {
if len(p) < 1 || len(p) > 256 {
e.err = FormatError("bad palette length: " + strconv.Itoa(len(p)))
return
}
- for i, c := range p {
- r, g, b, _ := c.RGBA()
- e.tmp[3*i+0] = uint8(r >> 8)
- e.tmp[3*i+1] = uint8(g >> 8)
- e.tmp[3*i+2] = uint8(b >> 8)
- }
- e.writeChunk(e.tmp[0:3*len(p)], "PLTE")
-}
-
-func (e *encoder) maybeWritetRNS(p color.Palette) {
last := -1
for i, c := range p {
- _, _, _, a := c.RGBA()
- if a != 0xffff {
+ c1 := color.NRGBAModel.Convert(c).(color.NRGBA)
+ e.tmp[3*i+0] = c1.R
+ e.tmp[3*i+1] = c1.G
+ e.tmp[3*i+2] = c1.B
+ if c1.A != 0xff {
last = i
}
- e.tmp[i] = uint8(a >> 8)
+ e.tmp[3*256+i] = c1.A
}
- if last == -1 {
- return
+ e.writeChunk(e.tmp[:3*len(p)], "PLTE")
+ if last != -1 {
+ e.writeChunk(e.tmp[3*256:3*256+1+last], "tRNS")
}
- e.writeChunk(e.tmp[:last+1], "tRNS")
}
// An encoder is an io.Writer that satisfies writes by writing PNG IDAT chunks,
@@ -412,7 +405,7 @@ func (e *encoder) writeIDATs() {
e.err = bw.Flush()
}
-func (e *encoder) writeIEND() { e.writeChunk(e.tmp[0:0], "IEND") }
+func (e *encoder) writeIEND() { e.writeChunk(nil, "IEND") }
// Encode writes the Image m to w in PNG format. Any Image may be encoded, but
// images that are not image.NRGBA might be encoded lossily.
@@ -460,8 +453,7 @@ func Encode(w io.Writer, m image.Image) error {
_, e.err = io.WriteString(w, pngHeader)
e.writeIHDR()
if pal != nil {
- e.writePLTE(pal)
- e.maybeWritetRNS(pal)
+ e.writePLTEAndTRNS(pal)
}
e.writeIDATs()
e.writeIEND()
コアとなるコードの解説
src/pkg/image/png/reader.go
の変更
- 変更前:
d.palette[i] = color.RGBA{rgba.R, rgba.G, rgba.B, d.tmp[i]}
- 変更後:
d.palette[i] = color.NRGBA{rgba.R, rgba.G, rgba.B, d.tmp[i]}
parsetRNS
関数は、tRNSチャンクから読み取ったアルファ値をパレットの色に適用する部分です。変更前はcolor.RGBA
型を使用していましたが、これはGoのimage
パッケージの内部でアルファ乗算済みとして扱われる可能性がありました。PNGのtRNSチャンクの仕様では、アルファ値は非アルファ乗算済みであるため、明示的にcolor.NRGBA
型を使用するように変更されました。これにより、デコード時にtRNSチャンクの透明度情報が正しく解釈され、パレットの色に適用されるようになります。
src/pkg/image/png/reader_test.go
の変更
このテストファイルでは、パレットの色からRGBとアルファ値を取得するロジックが変更されています。
- 変更前:
r, g, b, a := c.RGBA()
- 変更後:
switch c := c.(type) { ... }
を用いてcolor.RGBA
とcolor.NRGBA
の両方に対応し、それぞれの型からR, G, B, A成分を直接取得するように変更。
これは、c.RGBA()
が常に64ビットのアルファ乗算済みRGBA値を返すため、テストコードがパレットの実際の非アルファ乗算済みアルファ値を正しく検証できない可能性があったためです。新しいコードでは、パレットの色がcolor.RGBA
またはcolor.NRGBA
のどちらであるかに応じて、適切な方法でR, G, B, A成分を取得し、特にアルファ値が0xff
(完全に不透明)でない場合にlastAlpha
を更新するようにしています。これにより、tRNSチャンクのテストがより正確に行えるようになります。
src/pkg/image/png/writer.go
の変更
-
tmp
バッファのサイズ変更:- 変更前:
tmp [3 * 256]byte
- 変更後:
tmp [4 * 256]byte
パレットの色情報(RGB各3バイト)に加えて、tRNSチャンクのアルファ情報(1バイト)を格納するために、一時バッファのサイズが拡張されました。これにより、writePLTEAndTRNS
関数内でパレットの色とtRNSチャンクのアルファ値を効率的に格納できるようになります。
- 変更前:
-
スライス操作の簡略化:
e.header[0:4]
->e.header[:4]
e.footer[0:4]
->e.footer[:4]
e.header[0:8]
->e.header[:8]
e.footer[0:4]
->e.footer[:4]
e.tmp[0:13]
->e.tmp[:13]
これは機能的な変更ではなく、Go言語におけるスライス操作の慣用的な書き方への変更であり、コードの可読性を向上させます。
-
writePLTE
とmaybeWritetRNS
関数の統合とロジック変更:- 変更前:
writePLTE
とmaybeWritetRNS
の2つの関数でパレットとtRNSチャンクを別々に処理。 - 変更後:
writePLTEAndTRNS
という1つの関数に統合。 この統合された関数内で、パレットの色をループ処理し、各色をcolor.NRGBAModel.Convert(c).(color.NRGBA)
で明示的にcolor.NRGBA
に変換しています。これにより、パレットの色から取得されるRGBおよびアルファ値が非アルファ乗算済みであることが保証されます。そして、これらの値を使ってPLTEチャンクとtRNSチャンクのデータを構築し、書き出します。特に、tRNSチャンクのデータはe.tmp[3*256+i] = c1.A
のように、拡張されたtmp
バッファの後半部分に格納されます。last
変数を使って、実際に透明な色が存在する場合のみtRNSチャンクを書き出すロジックも維持されています。
- 変更前:
-
writeIEND
関数の変更:- 変更前:
e.writeChunk(e.tmp[0:0], "IEND")
- 変更後:
e.writeChunk(nil, "IEND")
IEND
チャンクはデータを持たないため、空のスライスを渡す代わりにnil
を渡すように変更されました。これは機能的な違いはありませんが、より簡潔な記述です。
- 変更前:
-
Encode
関数の変更:- 変更前:
e.writePLTE(pal)
とe.maybeWritetRNS(pal)
を個別に呼び出し。 - 変更後:
e.writePLTEAndTRNS(pal)
を呼び出し。writePLTE
とmaybeWritetRNS
がwritePLTEAndTRNS
に統合されたため、Encode
関数からの呼び出しもそれに合わせて変更されました。
- 変更前:
これらの変更により、Goのimage/png
パッケージはPNGのtRNSチャンクの仕様(非アルファ乗算済み)に厳密に準拠するようになり、透明度を持つPNG画像のエンコードとデコードがより正確に行われるようになりました。
関連リンク
- PNG (Portable Network Graphics) Specification - W3C Recommendation
- 特に、tRNSチャンクに関するセクション(4.3.3. tRNS Transparency chunk)を参照。
- GoDoc: image/png package
- GoDoc: image/color package
- Wikipedia: アルファブレンド
- Premultiplied Alpha
参考にした情報源リンク
- 上記の「関連リンク」セクションに記載されている公式ドキュメントおよびWikipediaのページ。
- Go言語の
image
パッケージおよびimage/png
パッケージのソースコード。 - PNGフォーマットに関する一般的な技術解説記事。
- アルファ乗算済みと非アルファ乗算済みアルファに関するグラフィックスプログラミングの解説記事。
[インデックス 13546] ファイルの概要
このコミットは、Go言語の標準ライブラリであるimage/png
パッケージにおける、PNG画像の透明度情報(tRNSチャンク)の取り扱いに関する修正です。具体的には、tRNSチャンクが「非アルファ乗算済み(non-alpha-premultiplied)」であることを明確にし、それに基づいてエンコーダとデコーダの挙動を調整しています。これにより、PNG画像の透明度が正しく処理されるようになります。
コミット
commit 695024b8fa78677362ad2c3d57fa63de7f5fbab4
Author: Nigel Tao <nigeltao@golang.org>
Date: Wed Aug 1 09:20:44 2012 +1000
image/png: tRNS chunk is *non*-alpha-premultiplied.
R=r
CC=golang-dev
https://golang.org/cl/6446062
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/695024b8fa78677362ad2c3d57fa63de7f5fbab4
元コミット内容
image/png: tRNS chunk is *non*-alpha-premultiplied.
このコミットメッセージは、PNGのtRNSチャンクがアルファ乗算されていない(non-alpha-premultiplied)形式であることを明示し、それに対応する修正が行われたことを示しています。
変更の背景
PNG画像フォーマットにおいて、透明度(アルファチャンネル)の表現方法には「アルファ乗算済み(premultiplied alpha)」と「非アルファ乗算済み(non-premultiplied alpha)」の2種類があります。
- 非アルファ乗算済み(Non-premultiplied alpha): 各ピクセルのRGB値が、そのピクセルのアルファ値によって乗算されていない状態です。これは、色情報と透明度情報が独立して保持されていることを意味します。PNGの仕様では、tRNSチャンク(パレットベースの画像やグレースケール、RGB画像における単一の透明色を指定するチャンク)で指定される透明度は、この非アルファ乗算済み形式であるとされています。
- アルファ乗算済み(Premultiplied alpha): 各ピクセルのRGB値が、そのピクセルのアルファ値によって事前に乗算されている状態です。これは、合成処理(ブレンディング)を行う際に計算が効率的になるという利点がありますが、色情報と透明度情報が密接に結合しています。
Goのimage/png
パッケージでは、tRNSチャンクのデータを処理する際に、この「非アルファ乗算済み」という特性が正しく考慮されていなかった可能性があります。その結果、透明度が期待通りに適用されず、画像が正しく表示されない、または意図しない色になるなどの問題が発生していたと考えられます。このコミットは、この誤解釈を修正し、PNGの仕様に厳密に準拠することで、透明度処理の正確性を向上させることを目的としています。
前提知識の解説
PNG (Portable Network Graphics)
PNGは、可逆圧縮を特徴とするビットマップ画像フォーマットです。ウェブ上で広く利用されており、透明度(アルファチャンネル)をサポートしている点が大きな特徴です。PNGファイルは、複数の「チャンク」と呼ばれるデータブロックで構成されており、それぞれが特定の情報(画像ヘッダ、パレット、画像データなど)を格納しています。
tRNSチャンク (Transparency Chunk)
tRNSチャンクは、PNGファイルにおいて透明度情報を定義するために使用されるオプションのチャンクです。その用途は、画像のカラータイプによって異なります。
- パレットベースの画像 (Color Type 3): パレット内の各エントリ(色)に対して、個別のアルファ値を指定します。これにより、パレット内の特定の色を部分的に透明にしたり、完全に透明にしたりすることができます。
- グレースケール画像 (Color Type 0) および RGB画像 (Color Type 2): 単一の透明色を指定します。この色と完全に一致するピクセルは、完全に透明として扱われます。
このコミットの文脈では、特にパレットベースの画像におけるtRNSチャンクの扱いが重要です。
アルファチャンネルと透明度
アルファチャンネルは、画像の各ピクセルの透明度を表現するための情報です。通常、0(完全に透明)から255(完全に不透明)までの値で表されます。
color.RGBA
と color.NRGBA
(Go言語のimage
パッケージ)
Go言語のimage
パッケージには、色を表現するための様々な型が定義されています。
color.RGBA
: 赤(Red)、緑(Green)、青(Blue)、アルファ(Alpha)の各成分を8ビット(0-255)で表現する色型です。この型は、アルファ乗算されていない色を表します。つまり、RGB値はアルファ値とは独立しています。color.NRGBA
: 非アルファ乗算済み(Non-premultiplied RGBA)の色型です。color.RGBA
と同様に各成分を8ビットで表現しますが、この型は特に「非アルファ乗算済み」であることを明示的に示します。image
パッケージ内での内部的な処理において、アルファ乗算済みと非アルファ乗算済みの区別は重要です。
このコミットでは、tRNSチャンクのデータがcolor.NRGBA
として解釈されるべきであるという認識に基づき、コードが修正されています。
技術的詳細
このコミットの核心は、PNGのtRNSチャンクが非アルファ乗算済みであるという仕様をGoのimage/png
パッケージの実装に正しく反映させることです。
image/png
パッケージは、PNGファイルの読み込み(デコード)と書き込み(エンコード)を処理します。
デコード時、reader.go
のparsetRNS
関数は、tRNSチャンクから読み取ったアルファ値をパレットの色に適用します。元のコードでは、パレットの色をcolor.RGBA
として扱い、そのアルファ値を更新していました。しかし、color.RGBA
は非アルファ乗算済みを意図する型であるものの、内部的な処理でアルファ乗算済みとして扱われる可能性がありました。この修正では、明示的にcolor.NRGBA
型を使用することで、tRNSチャンクのアルファ値が非アルファ乗算済みとして正しくパレットに適用されるように変更されています。
エンコード時、writer.go
のwritePLTEAndTRNS
関数は、パレットとtRNSチャンクを書き出します。元のコードでは、パレットの色からRGBとアルファ値を取得する際に、c.RGBA()
メソッドを使用していました。このメソッドは、色を64ビットのアルファ乗算済みRGBA値として返すため、非アルファ乗算済みのtRNSチャンクのデータとしては不適切でした。修正後では、パレットの色をcolor.NRGBAModel.Convert(c).(color.NRGBA)
によって明示的にcolor.NRGBA
に変換し、そのR, G, B, A成分を直接使用することで、非アルファ乗算済みの正しい値をtRNSチャンクに書き込むようにしています。
また、writer.go
では、一時バッファtmp
のサイズが3 * 256
バイトから4 * 256
バイトに拡張されています。これは、パレットの色情報(RGB各3バイト)に加えて、tRNSチャンクのアルファ情報(1バイト)を格納するために必要なスペースを確保するためです。これにより、writePLTEAndTRNS
関数内でパレットの色とtRNSチャンクのアルファ値を効率的に格納し、書き出すことが可能になります。
さらに、スライス操作の簡略化(例: e.header[0:4]
からe.header[:4]
)や、writeIEND
関数での空のバイトスライスe.tmp[0:0]
の代わりにnil
を渡す変更も行われています。これらは機能的な変更ではなく、コードの可読性や慣用的なGoの書き方に合わせた改善です。
コアとなるコードの変更箇所
src/pkg/image/png/reader.go
--- a/src/pkg/image/png/reader.go
+++ b/src/pkg/image/png/reader.go
@@ -226,7 +226,7 @@ func (d *decoder) parsetRNS(length uint32) error {
}
for i := 0; i < n; i++ {
rgba := d.palette[i].(color.RGBA)
- d.palette[i] = color.RGBA{rgba.R, rgba.G, rgba.B, d.tmp[i]}
+ d.palette[i] = color.NRGBA{rgba.R, rgba.G, rgba.B, d.tmp[i]}
}
case cbGA8, cbGA16, cbTCA8, cbTCA16:
return FormatError("tRNS, color type mismatch")
src/pkg/image/png/reader_test.go
--- a/src/pkg/image/png/reader_test.go
+++ b/src/pkg/image/png/reader_test.go
@@ -107,13 +107,18 @@ func sng(w io.WriteCloser, filename string, png image.Image) {
lastAlpha := -1
io.WriteString(w, "PLTE {\n")
for i, c := range cpm {
- r, g, b, a := c.RGBA()
- if a != 0xffff {
+ var r, g, b, a uint8
+ switch c := c.(type) {
+ case color.RGBA:
+ r, g, b, a = c.R, c.G, c.B, 0xff
+ case color.NRGBA:
+ r, g, b, a = c.R, c.G, c.B, c.A
+ default:
+ panic("unknown palette color type")
+ }
+ if a != 0xff {
lastAlpha = i
}
- r >>= 8
- g >>= 8
- b >>= 8
fmt.Fprintf(w, " (%3d,%3d,%3d) # rgb = (0x%02x,0x%02x,0x%02x)\\n", r, g, b, r, g, b)
}
io.WriteString(w, "}\n")
src/pkg/image/png/writer.go
--- a/src/pkg/image/png/writer.go
+++ b/src/pkg/image/png/writer.go
@@ -21,7 +21,7 @@ type encoder struct {
err error
header [8]byte
footer [4]byte
- tmp [3 * 256]byte
+ tmp [4 * 256]byte
}
// Big-endian.
@@ -70,7 +70,7 @@ func (e *encoder) writeChunk(b []byte, name string) {
e.err = UnsupportedError(name + " chunk is too large: " + strconv.Itoa(len(b)))
return
}
- writeUint32(e.header[0:4], n)
+ writeUint32(e.header[:4], n)
e.header[4] = name[0]
e.header[5] = name[1]
e.header[6] = name[2]
@@ -78,9 +78,9 @@ func (e *encoder) writeChunk(b []byte, name string) {
crc := crc32.NewIEEE()
crc.Write(e.header[4:8])
crc.Write(b)
- writeUint32(e.footer[0:4], crc.Sum32())
+ writeUint32(e.footer[:4], crc.Sum32())
- _, e.err = e.w.Write(e.header[0:8])
+ _, e.err = e.w.Write(e.header[:8])
if e.err != nil {
return
}
@@ -88,7 +88,7 @@ func (e *encoder) writeChunk(b []byte, name string) {
if e.err != nil {
return
}
- _, e.err = e.w.Write(e.footer[0:4])
+ _, e.err = e.w.Write(e.footer[:4])
}
func (e *encoder) writeIHDR() {
@@ -122,36 +122,29 @@ func (e *encoder) writeIHDR() {
e.tmp[10] = 0 // default compression method
e.tmp[11] = 0 // default filter method
e.tmp[12] = 0 // non-interlaced
- e.writeChunk(e.tmp[0:13], "IHDR")
+ e.writeChunk(e.tmp[:13], "IHDR")
}
-func (e *encoder) writePLTE(p color.Palette) {
+func (e *encoder) writePLTEAndTRNS(p color.Palette) {
if len(p) < 1 || len(p) > 256 {
e.err = FormatError("bad palette length: " + strconv.Itoa(len(p)))
return
}
- for i, c := range p {
- r, g, b, _ := c.RGBA()
- e.tmp[3*i+0] = uint8(r >> 8)
- e.tmp[3*i+1] = uint8(g >> 8)
- e.tmp[3*i+2] = uint8(b >> 8)
- }
- e.writeChunk(e.tmp[0:3*len(p)], "PLTE")
-}
-
-func (e *encoder) maybeWritetRNS(p color.Palette) {
last := -1
for i, c := range p {
- _, _, _, a := c.RGBA()
- if a != 0xffff {
+ c1 := color.NRGBAModel.Convert(c).(color.NRGBA)
+ e.tmp[3*i+0] = c1.R
+ e.tmp[3*i+1] = c1.G
+ e.tmp[3*i+2] = c1.B
+ if c1.A != 0xff {
last = i
}
- e.tmp[i] = uint8(a >> 8)
+ e.tmp[3*256+i] = c1.A
}
- if last == -1 {
- return
+ e.writeChunk(e.tmp[:3*len(p)], "PLTE")
+ if last != -1 {
+ e.writeChunk(e.tmp[3*256:3*256+1+last], "tRNS")
}
- e.writeChunk(e.tmp[:last+1], "tRNS")
}
// An encoder is an io.Writer that satisfies writes by writing PNG IDAT chunks,
@@ -412,7 +405,7 @@ func (e *encoder) writeIDATs() {
e.err = bw.Flush()
}
-func (e *encoder) writeIEND() { e.writeChunk(e.tmp[0:0], "IEND") }\n+func (e *encoder) writeIEND() { e.writeChunk(nil, "IEND") }\n \n // Encode writes the Image m to w in PNG format. Any Image may be encoded, but\n // images that are not image.NRGBA might be encoded lossily.\n@@ -460,8 +453,7 @@ func Encode(w io.Writer, m image.Image) error {\n _, e.err = io.WriteString(w, pngHeader)\n e.writeIHDR()\n if pal != nil {\n-\t\te.writePLTE(pal)\n-\t\te.maybeWritetRNS(pal)\n+\t\te.writePLTEAndTRNS(pal)\n \t}\n e.writeIDATs()\n e.writeIEND()\n```
## コアとなるコードの解説
### `src/pkg/image/png/reader.go` の変更
* **変更前**: `d.palette[i] = color.RGBA{rgba.R, rgba.G, rgba.B, d.tmp[i]}`
* **変更後**: `d.palette[i] = color.NRGBA{rgba.R, rgba.G, rgba.B, d.tmp[i]}`
`parsetRNS`関数は、tRNSチャンクから読み取ったアルファ値をパレットの色に適用する部分です。変更前は`color.RGBA`型を使用していましたが、これはGoの`image`パッケージの内部でアルファ乗算済みとして扱われる可能性がありました。PNGのtRNSチャンクの仕様では、アルファ値は非アルファ乗算済みであるため、明示的に`color.NRGBA`型を使用するように変更されました。これにより、デコード時にtRNSチャンクの透明度情報が正しく解釈され、パレットの色に適用されるようになります。
### `src/pkg/image/png/reader_test.go` の変更
このテストファイルでは、パレットの色からRGBとアルファ値を取得するロジックが変更されています。
* **変更前**: `r, g, b, a := c.RGBA()`
* **変更後**: `switch c := c.(type) { ... }` を用いて `color.RGBA` と `color.NRGBA` の両方に対応し、それぞれの型からR, G, B, A成分を直接取得するように変更。
これは、`c.RGBA()`が常に64ビットのアルファ乗算済みRGBA値を返すため、テストコードがパレットの実際の非アルファ乗算済みアルファ値を正しく検証できない可能性があったためです。新しいコードでは、パレットの色が`color.RGBA`または`color.NRGBA`のどちらであるかに応じて、適切な方法でR, G, B, A成分を取得し、特にアルファ値が`0xff`(完全に不透明)でない場合に`lastAlpha`を更新するようにしています。これにより、tRNSチャンクのテストがより正確に行えるようになります。
### `src/pkg/image/png/writer.go` の変更
1. **`tmp` バッファのサイズ変更**:
* **変更前**: `tmp [3 * 256]byte`
* **変更後**: `tmp [4 * 256]byte`
パレットの色情報(RGB各3バイト)に加えて、tRNSチャンクのアルファ情報(1バイト)を格納するために、一時バッファのサイズが拡張されました。これにより、`writePLTEAndTRNS`関数内でパレットの色とtRNSチャンクのアルファ値を効率的に格納できるようになります。
2. **スライス操作の簡略化**:
* `e.header[0:4]` -> `e.header[:4]`
* `e.footer[0:4]` -> `e.footer[:4]`
* `e.header[0:8]` -> `e.header[:8]`
* `e.footer[0:4]` -> `e.footer[:4]`
* `e.tmp[0:13]` -> `e.tmp[:13]`
これは機能的な変更ではなく、Go言語におけるスライス操作の慣用的な書き方への変更であり、コードの可読性を向上させます。
3. **`writePLTE` と `maybeWritetRNS` 関数の統合とロジック変更**:
* **変更前**: `writePLTE` と `maybeWritetRNS` の2つの関数でパレットとtRNSチャンクを別々に処理。
* **変更後**: `writePLTEAndTRNS` という1つの関数に統合。
この統合された関数内で、パレットの色をループ処理し、各色を`color.NRGBAModel.Convert(c).(color.NRGBA)`で明示的に`color.NRGBA`に変換しています。これにより、パレットの色から取得されるRGBおよびアルファ値が非アルファ乗算済みであることが保証されます。そして、これらの値を使ってPLTEチャンクとtRNSチャンクのデータを構築し、書き出します。特に、tRNSチャンクのデータは`e.tmp[3*256+i] = c1.A`のように、拡張された`tmp`バッファの後半部分に格納されます。`last`変数を使って、実際に透明な色が存在する場合のみtRNSチャンクを書き出すロジックも維持されています。
4. **`writeIEND` 関数の変更**:
* **変更前**: `e.writeChunk(e.tmp[0:0], "IEND")`
* **変更後**: `e.writeChunk(nil, "IEND")`
`IEND`チャンクはデータを持たないため、空のスライスを渡す代わりに`nil`を渡すように変更されました。これは機能的な違いはありませんが、より簡潔な記述です。
5. **`Encode` 関数の変更**:
* **変更前**: `e.writePLTE(pal)` と `e.maybeWritetRNS(pal)` を個別に呼び出し。
* **変更後**: `e.writePLTEAndTRNS(pal)` を呼び出し。
`writePLTE`と`maybeWritetRNS`が`writePLTEAndTRNS`に統合されたため、`Encode`関数からの呼び出しもそれに合わせて変更されました。
これらの変更により、Goの`image/png`パッケージはPNGのtRNSチャンクの仕様(非アルファ乗算済み)に厳密に準拠するようになり、透明度を持つPNG画像のエンコードとデコードがより正確に行われるようになりました。
## 関連リンク
* [PNG (Portable Network Graphics) Specification - W3C Recommendation](https://www.w3.org/TR/PNG/)
* 特に、tRNSチャンクに関するセクション(4.3.3. tRNS Transparency chunk)を参照。
* [GoDoc: image/png package](https://pkg.go.dev/image/png)
* [GoDoc: image/color package](https://pkg.go.dev/image/color)
* [Wikipedia: アルファブレンド](https://ja.wikipedia.org/wiki/%E3%82%A2%E3%83%AB%E3%83%95%E3%82%A1%E3%83%96%E3%83%AC%E3%83%B3%E3%83%89)
* [Premultiplied Alpha](https://en.wikipedia.org/wiki/Alpha_compositing#Premultiplied_alpha)
## 参考にした情報源リンク
* 上記の「関連リンク」セクションに記載されている公式ドキュメントおよびWikipediaのページ。
* Go言語の`image`パッケージおよび`image/png`パッケージのソースコード。
* PNGフォーマットに関する一般的な技術解説記事。
* アルファ乗算済みと非アルファ乗算済みアルファに関するグラフィックスプログラミングの解説記事。