colorm: Add ColorM.ChangeHSV and an example

This commit is contained in:
Hajime Hoshi 2016-03-04 01:35:03 +09:00
parent 05f52b5e57
commit 723876feba
2 changed files with 156 additions and 19 deletions

View File

@ -104,19 +104,57 @@ func (c *ColorM) Translate(r, g, b, a float64) {
// RotateHue rotates the hue. // RotateHue rotates the hue.
func (c *ColorM) RotateHue(theta float64) { func (c *ColorM) RotateHue(theta float64) {
sin, cos := math.Sincos(theta) c.ChangeHSV(theta, 1, 1)
v1 := cos + (1.0-cos)/3.0 }
v2 := (1.0/3.0)*(1.0-cos) - math.Sqrt(1.0/3.0)*sin
v3 := (1.0/3.0)*(1.0-cos) + math.Sqrt(1.0/3.0)*sin var (
// The YCbCr value ranges are:
// Y: [ 0 - 1 ]
// Cb: [-0.5 - 0.5]
// Cr: [-0.5 - 0.5]
rgbToYCbCr = ColorM{
initialized: true,
es: [ColorMDim - 1][ColorMDim]float64{
{0.2990, 0.5870, 0.1140, 0, 0},
{-0.1687, -0.3313, 0.5000, 0, 0},
{0.5000, -0.4187, -0.0813, 0, 0},
{0, 0, 0, 1, 0},
},
}
yCbCrToRgb = ColorM{
initialized: true,
es: [ColorMDim - 1][ColorMDim]float64{
{1, 0, 1.40200, 0, 0},
{1, -0.34414, -0.71414, 0, 0},
{1, 1.77200, 0, 0, 0},
{0, 0, 0, 1, 0},
},
}
)
// ChangeHSV changes HSV (Hue-Saturation-Value) values.
// hueTheta is a radian value to ratate hue.
// saturationScale is a value to scale saturation.
// valueScale is a value to scale value (a.k.a. brightness).
//
// This conversion uses RGB to/from YCrCb conversion.
func (c *ColorM) ChangeHSV(hueTheta float64, saturationScale float64, valueScale float64) {
sin, cos := math.Sincos(hueTheta)
c.Concat(rgbToYCbCr)
c.Concat(ColorM{ c.Concat(ColorM{
initialized: true, initialized: true,
es: [ColorMDim - 1][ColorMDim]float64{ es: [ColorMDim - 1][ColorMDim]float64{
{v1, v2, v3, 0, 0}, {1, 0, 0, 0, 0},
{v3, v1, v2, 0, 0}, {0, cos, -sin, 0, 0},
{v2, v3, v1, 0, 0}, {0, sin, cos, 0, 0},
{0, 0, 0, 1, 0}, {0, 0, 0, 1, 0},
}, },
}) })
s := saturationScale
v := valueScale
c.Scale(v, s*v, s*v, 1)
c.Concat(yCbCrToRgb)
} }
// SetElement sets an element at (i, j). // SetElement sets an element at (i, j).
@ -127,20 +165,15 @@ func (c *ColorM) SetElement(i, j int, element float64) {
c.es[i][j] = element c.es[i][j] = element
} }
var monochrome ColorM
func init() {
monochrome.ChangeHSV(0, 0, 1)
}
// Monochrome returns a color matrix to make an image monochrome. // Monochrome returns a color matrix to make an image monochrome.
func Monochrome() ColorM { func Monochrome() ColorM {
const r = 6968.0 / 32768.0 return monochrome
const g = 23434.0 / 32768.0
const b = 2366.0 / 32768.0
return ColorM{
initialized: true,
es: [ColorMDim - 1][ColorMDim]float64{
{r, g, b, 0, 0},
{r, g, b, 0, 0},
{r, g, b, 0, 0},
{0, 0, 0, 1, 0},
},
}
} }
// Deprecated as of 1.2.0-alpha. Use Scale instead. // Deprecated as of 1.2.0-alpha. Use Scale instead.

104
examples/hsv/main.go Normal file
View File

@ -0,0 +1,104 @@
// Copyright 2016 Hajime Hoshi
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"fmt"
_ "image/jpeg"
"log"
"math"
"github.com/hajimehoshi/ebiten"
"github.com/hajimehoshi/ebiten/ebitenutil"
)
const (
screenWidth = 320
screenHeight = 240
)
var (
hueInt = 0
saturationInt = 128
valueInt = 128
gophersImage *ebiten.Image
)
func clamp(v, min, max int) int {
if min > max {
panic("min must <= max")
}
if v < min {
return min
}
if max < v {
return max
}
return v
}
func update(screen *ebiten.Image) error {
if ebiten.IsKeyPressed(ebiten.KeyQ) {
hueInt--
}
if ebiten.IsKeyPressed(ebiten.KeyW) {
hueInt++
}
if ebiten.IsKeyPressed(ebiten.KeyA) {
saturationInt--
}
if ebiten.IsKeyPressed(ebiten.KeyS) {
saturationInt++
}
if ebiten.IsKeyPressed(ebiten.KeyZ) {
valueInt--
}
if ebiten.IsKeyPressed(ebiten.KeyX) {
valueInt++
}
hueInt = clamp(hueInt, -256, 256)
saturationInt = clamp(saturationInt, 0, 256)
valueInt = clamp(valueInt, 0, 256)
w, h := gophersImage.Size()
op := &ebiten.DrawImageOptions{}
op.GeoM.Translate(float64(screenWidth-w)/2, float64(screenHeight-h)/2)
hue := float64(hueInt) * 2 * math.Pi / 128
saturation := float64(saturationInt) / 128
value := float64(valueInt) / 128
op.ColorM.ChangeHSV(hue, saturation, value)
if err := screen.DrawImage(gophersImage, op); err != nil {
return err
}
msg := fmt.Sprintf(`Hue: %0.2f [Q][W]
Saturation: %0.2f [A][S]
Value: %0.2f [Z][X]`, hue, saturation, value)
if err := ebitenutil.DebugPrint(screen, msg); err != nil {
return err
}
return nil
}
func main() {
var err error
gophersImage, _, err = ebitenutil.NewImageFromFile("_resources/images/gophers.jpg", ebiten.FilterNearest)
if err != nil {
log.Fatal(err)
}
if err := ebiten.Run(update, screenWidth, screenHeight, 2, "HSV (Ebiten Demo)"); err != nil {
log.Fatal(err)
}
}