Po pomyślnym dekodowania image.Decode()
(a także specyficzne funkcje dekodowania jak jpeg.Decode()
) zwracają wartość image.Image
. image.Image
to interfejs, który definiuje widok tylko do odczytu obrazu: nie zapewnia metod zmiany/narysowania na obrazie.
Pakiet image
zawiera kilka implementacji image.Image
, które umożliwiają zmianę/rysowanie obrazu, zwykle za pomocą metody Set(x, y int, c color.Color)
.
image.Decode()
jednak nie daje żadnej gwarancji, że powrócił obraz zostanie którykolwiek z typów obrazów określonych w pakiecie image
, lub nawet, że dynamiczny typ obrazu ma Set()
metoda (może, ale nie ma gwarancji) . Zarejestrowane niestandardowe dekodery obrazu mogą zwrócić wartość image.Image
będącą niestandardową implementacją (co oznacza, że nie jest to typ obrazu zdefiniowany w pakiecie image
).
Jeśli obraz (typ dynamiczny) ma metodę Set()
, można użyć type assertion i użyć jej metody Set()
, aby narysować na niej.W ten sposób można to zrobić:
type Changeable interface {
Set(x, y int, c color.Color)
}
imgfile, err := os.Open("unchanged.jpg")
if err != nil {
panic(err.Error())
}
defer imgfile.Close()
img, err := jpeg.Decode(imgfile)
if err != nil {
panic(err.Error())
}
if cimg, ok := img.(Changeable); ok {
// cimg is of type Changeable, you can call its Set() method (draw on it)
cimg.Set(0, 0, color.RGBA{85, 165, 34, 255})
cimg.Set(0, 1, color.RGBA{255, 0, 0, 255})
// when done, save img as usual
} else {
// No luck... see your options below
}
Jeśli obraz nie ma metody Set()
, można zdecydować się na „zastąpić swój pogląd” poprzez wdrożenie typ niestandardowy, który implementuje image.Image
, ale w jego metodzie At(x, y int) color.Color
(co zwraca/dostarcza kolory pikseli) zwracasz nowe kolory, które ustawisz, jeśli obraz będzie podlegał zmianom, i zwrócisz piksele oryginalnego obrazu, w którym nie zmieniłbyś obrazu.
Wdrażanie interfejsu image.Image
jest najłatwiejsze dzięki wykorzystaniu osadzania, więc wystarczy wprowadzić wymagane zmiany. Oto, jak można to zrobić:
type MyImg struct {
// Embed image.Image so MyImg will implement image.Image
// because fields and methods of Image will be promoted:
image.Image
}
func (m *MyImg) At(x, y int) color.Color {
// "Changed" part: custom colors for specific coordinates:
switch {
case x == 0 && y == 0:
return color.RGBA{85, 165, 34, 255}
case x == 0 && y == 1:
return color.RGBA{255, 0, 0, 255}
}
// "Unchanged" part: the colors of the original image:
return m.Image.At(x, y)
}
Korzystanie z niego: niezwykle proste. Załadować obraz jak ty, ale podczas zapisywania, zapewniają wartość naszej MyImg
typu, który zadba o dostarczenie treści zmieniony obrazu (kolory), kiedy jest proszony przez koder:
jpeg.Encode(outFile, &MyImg{img}, nil)
Jeśli trzeba zmienić wiele pikseli, nie jest możliwe uwzględnienie wszystkich w metodzie At()
. W tym celu możemy rozszerzyć naszą MyImg
, aby mieć implementację Set()
, która przechowuje piksele, które chcemy zmienić. Przykład wdrożenia:
type MyImg struct {
image.Image
custom map[image.Point]color.Color
}
func NewMyImg(img image.Image) *MyImg {
return &MyImg{img, map[image.Point]color.Color{}}
}
func (m *MyImg) Set(x, y int, c color.Color) {
m.custom[image.Point{x, y}] = c
}
func (m *MyImg) At(x, y int) color.Color {
// Explicitly changed part: custom colors of the changed pixels:
if c := m.custom[image.Point{x, y}]; c != nil {
return c
}
// Unchanged part: colors of the original image:
return m.Image.At(x, y)
}
Używanie go:
// Load image as usual, then
my := NewMyImg(img)
my.Set(0, 0, color.RGBA{85, 165, 34, 1})
my.Set(0, 1, color.RGBA{255, 0, 0, 255})
// And when saving, save 'my' instead of the original:
jpeg.Encode(outFile, my, nil)
Jeśli trzeba zmienić wiele pikseli, wówczas może być bardziej opłaca się po prostu utworzyć nowy obraz, który obsługuje zmianę jego pikseli, na przykład image.RGBA
, narysuj na nim oryginalny obraz, a następnie przejdź do zmiany pikseli, które chcesz.
Aby narysować obraz na innym, można użyć pakietu image/draw
.
cimg := image.NewRGBA(img.Bounds())
draw.Draw(cimg, img.Bounds(), img, image.Point{}, draw.Over)
// Now you have cimg which contains the original image and is changeable
// (it has a Set() method)
cimg.Set(0, 0, color.RGBA{85, 165, 34, 255})
cimg.Set(0, 1, color.RGBA{255, 0, 0, 255})
// And when saving, save 'cimg' of course:
jpeg.Encode(outFile, cimg, nil)
Powyższy kod służy wyłącznie do demonstracji. W rzeczywistych obrazach Image.Bounds()
może zwracać prostokąt, który nie zaczyna się od punktu (0;0)
, w którym to przypadku konieczne byłoby pewne dostosowanie, aby działało.
Powiązane/Możliwe duplikaty [Rysuj prostokąt w języku golang?] (Http://stackoverflow.com/questions/28992396/draw-a-rectangle-in-golang) – icza
Aby być uczciwym, nie rozumiem tej odpowiedzi. Nawet po dwukrotnym przeczytaniu nie mam pojęcia, jak można użyć tej metody Set. –
Dobra, zobacz moją odpowiedź poniżej. – icza