2016-04-12 14 views
5

Chcę otworzyć plik obrazu JPEG, zakodować go, zmienić niektóre kolory pikseli, a następnie zapisać go z powrotem, tak jak był.Zmień kolor pojedynczego piksela - Idź do obrazu lg

chciałbym zrobić coś takiego

imgfile, err := os.Open("unchanged.jpeg") 
defer imgfile.Close() 
if err != nil { 
    fmt.Println(err.Error()) 
} 

img,err := jpeg.Decode(imgfile) 
if err != nil { 
    fmt.Println(err.Error()) 
} 
img.Set(0, 0, color.RGBA{85, 165, 34, 1}) 
img.Set(1,0,....) 

outFile, _ := os.Create("changed.jpeg") 
defer outFile.Close() 
jpeg.Encode(outFile, img, nil) 

ja po prostu nie może pochodzić z roztworu roboczego, ponieważ domyślny typ obrazu, który otrzymuję po zakodowaniu pliku obrazowego nie ustawiono metodę .

Czy ktoś może wyjaśnić, jak to zrobić? Wielkie dzięki.

+0

Powiązane/Możliwe duplikaty [Rysuj prostokąt w języku golang?] (Http://stackoverflow.com/questions/28992396/draw-a-rectangle-in-golang) – icza

+0

Aby być uczciwym, nie rozumiem tej odpowiedzi. Nawet po dwukrotnym przeczytaniu nie mam pojęcia, jak można użyć tej metody Set. –

+0

Dobra, zobacz moją odpowiedź poniżej. – icza

Odpowiedz

10

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.

+0

Nawet patrząc przez źródło go dla tych pakietów, nie wiem, w jaki sposób można było ustalić, że można utworzyć interfejs za pomocą Set na tym. Jak doszedłeś do tego wniosku? –

+0

@JustinSelf Różne konkretne 'image.Implementacje obrazu w pakiecie 'image' wszystkie mają metodę' Set (x, y int, c color.Color) '. Zatem funkcje dekodowania obrazów standardowej biblioteki najprawdopodobniej zwrócą implementację obrazu, która będzie miała tę metodę "Set()". Dlatego właśnie powinniśmy to najpierw sprawdzić. Jeśli obraz nie ma metody "Set()", możemy nazwać naszą niestandardową metodę 'Set()', która nam się podoba (nie ma znaczenia tak długo, jak tylko ją nazwiemy), użyłem tego samego podpis metody dla jasności. – icza

0

Dekodowanie obrazu zwraca wartość image interface, która ma metodę Bounds w celu uzyskania szerokości i wysokości obrazu w pikselach.

img, _, err := image.Decode(imgfile) 
if err != nil { 
    fmt.Println(err.Error()) 
} 
size := img.Bounds().Size() 

Po mają szerokość i wysokość można iterację pikseli z dwoma zagnieżdżonej pętli (jeden dla x i jeden dla y współrzędnych).

for x := 0; x < size.X; x++ { 
    for y := 0; y < size.Y; y++ { 
     color := color.RGBA{ 
      uint8(255 * x/size.X), 
      uint8(255 * y/size.Y), 
      55, 
      255} 
     m.Set(x, y, color) 
    } 
} 

Po zakończeniu manipulacji obrazem można zakodować plik. Ale ponieważ image.Image nie ma metody Set, można utworzyć nowy obraz RGBA, który zwraca strukturę RGBA, na której można użyć metody Set.

m := image.NewRGBA(image.Rect(0, 0, width, height)) 
outFile, err := os.Create("changed.jpg") 
if err != nil { 
    log.Fatal(err) 
} 
defer outFile.Close() 
png.Encode(outFile, m) 
+0

Nie mogę tego użyć. Nadal nie mogę użyć metody Set, ponieważ mój zakodowany obraz jest typu image.Image, który nie ma metody Set. Zapętlanie całego obrazu nie jest tym, co chciałbym zrobić, potrzebuję zmienić zaledwie kilka pikseli. Dzięki za odpowiedź i tak –

+0

Co zrobić, aby utworzyć nowy obraz RGBA, który zwraca interfejs RGBA, na którym można użyć metody 'Set'' m: = image.NewRGBA (image.Rect (0, 0, szerokość, wysokość)) ' –

+0

Po prostu pomyślałem, że mogę narysować piksele bezpośrednio na moim zakodowanym obrazie. Tworzenie nowego obrazu i kopiowanie zawartości starego zakodowanego obrazu tylko po to, aby zmienić kilka pikseli wydaje mi się nieco dziwne. –

0

image.Image jest domyślnie niezmienny, ale draw.Image jest zmienny.

Jeśli do konwersji typu, aby draw.Image, które powinny dać metody określonej

img.(draw.Image).Set(0,0, color.RGBA{85, 165, 34, 1}) 
+2

Próbowałem zrobić to wcześniej. Otrzymuję ten błąd: "* image.YCbCr nie jest draw.Image: brak metody Set" –