1
1
mirror of https://github.com/OpenDiablo2/OpenDiablo2 synced 2024-06-09 01:10:43 +00:00

Finished font rendering with color mod support.

This commit is contained in:
Tim Sarbin 2019-10-24 19:13:30 -04:00
parent ae3062c38b
commit 01524fdb1d
4 changed files with 169 additions and 32 deletions

76
ColorConvert.go Normal file
View File

@ -0,0 +1,76 @@
package OpenDiablo2
import (
"image/color"
"math"
"sync"
"github.com/hajimehoshi/ebiten"
)
type colorMCacheKey uint32
type colorMCacheEntry struct {
m ebiten.ColorM
atime int64
}
var (
textM sync.Mutex
colorMCache = map[colorMCacheKey]*colorMCacheEntry{}
emptyColorM ebiten.ColorM
monotonicClock int64
cacheLimit = 512
)
func init() {
emptyColorM.Scale(0, 0, 0, 0)
}
func now() int64 {
monotonicClock++
return monotonicClock
}
func ColorToColorM(clr color.Color) ebiten.ColorM {
// RGBA() is in [0 - 0xffff]. Adjust them in [0 - 0xff].
cr, cg, cb, ca := clr.RGBA()
cr >>= 8
cg >>= 8
cb >>= 8
ca >>= 8
if ca == 0 {
return emptyColorM
}
key := colorMCacheKey(uint32(cr) | (uint32(cg) << 8) | (uint32(cb) << 16) | (uint32(ca) << 24))
e, ok := colorMCache[key]
if ok {
e.atime = now()
return e.m
}
if len(colorMCache) > cacheLimit {
oldest := int64(math.MaxInt64)
oldestKey := colorMCacheKey(0)
for key, c := range colorMCache {
if c.atime < oldest {
oldestKey = key
oldest = c.atime
}
}
delete(colorMCache, oldestKey)
}
cm := ebiten.ColorM{}
rf := float64(cr) / float64(ca)
gf := float64(cg) / float64(ca)
bf := float64(cb) / float64(ca)
af := float64(ca) / 0xff
cm.Scale(rf, gf, bf, af)
e = &colorMCacheEntry{
m: cm,
atime: now(),
}
colorMCache[key] = e
return e.m
}

View File

@ -1,6 +1,8 @@
package OpenDiablo2 package OpenDiablo2
import ( import (
"image/color"
"github.com/essial/OpenDiablo2/ResourcePaths" "github.com/essial/OpenDiablo2/ResourcePaths"
"github.com/hajimehoshi/ebiten" "github.com/hajimehoshi/ebiten"
) )
@ -14,6 +16,7 @@ type MainMenu struct {
DiabloLogoLeftBack Sprite DiabloLogoLeftBack Sprite
DiabloLogoRightBack Sprite DiabloLogoRightBack Sprite
CopyrightLabel *UILabel CopyrightLabel *UILabel
CopyrightLabel2 *UILabel
ShowTrademarkScreen bool ShowTrademarkScreen bool
} }
@ -28,34 +31,58 @@ func CreateMainMenu(engine *Engine) *MainMenu {
func (v *MainMenu) Load() { func (v *MainMenu) Load() {
go func() { go func() {
loadStep := 1.0 / 7.0 loadStep := 1.0 / 8.0
v.Engine.LoadingProgress = 0 v.Engine.LoadingProgress = 0
v.CopyrightLabel = CreateUILabel(v.Engine, ResourcePaths.FontFormal11, "static") {
v.CopyrightLabel.SetText("Hello, world!") v.CopyrightLabel = CreateUILabel(v.Engine, ResourcePaths.FontFormal12, "static")
v.CopyrightLabel.MoveTo(0, 0) v.CopyrightLabel.Alignment = UILabelAlignCenter
v.Engine.LoadingProgress += loadStep v.CopyrightLabel.SetText("Diablo 2 is © Copyright 2000-2016 Blizzard Entertainment")
v.Background = v.Engine.LoadSprite(ResourcePaths.GameSelectScreen, v.Engine.Palettes["sky"]) v.CopyrightLabel.ColorMod = color.RGBA{188, 168, 140, 255}
v.Background.MoveTo(0, 0) v.CopyrightLabel.MoveTo(400, 500)
v.Engine.LoadingProgress += loadStep v.Engine.LoadingProgress += loadStep
v.TrademarkBackground = v.Engine.LoadSprite(ResourcePaths.TrademarkScreen, v.Engine.Palettes["sky"]) }
v.TrademarkBackground.MoveTo(0, 0) {
v.Engine.LoadingProgress += loadStep v.CopyrightLabel2 = CreateUILabel(v.Engine, ResourcePaths.FontFormal12, "static")
v.DiabloLogoLeft = v.Engine.LoadSprite(ResourcePaths.Diablo2LogoFireLeft, v.Engine.Palettes["units"]) v.CopyrightLabel2.Alignment = UILabelAlignCenter
v.DiabloLogoLeft.Blend = true v.CopyrightLabel2.SetText("All Rights Reserved.")
v.DiabloLogoLeft.Animate = true v.CopyrightLabel2.ColorMod = color.RGBA{188, 168, 140, 255}
v.DiabloLogoLeft.MoveTo(400, 120) v.CopyrightLabel2.MoveTo(400, 525)
v.Engine.LoadingProgress += loadStep v.Engine.LoadingProgress += loadStep
v.DiabloLogoRight = v.Engine.LoadSprite(ResourcePaths.Diablo2LogoFireRight, v.Engine.Palettes["units"]) }
v.DiabloLogoRight.Blend = true {
v.DiabloLogoRight.Animate = true v.Background = v.Engine.LoadSprite(ResourcePaths.GameSelectScreen, v.Engine.Palettes["sky"])
v.DiabloLogoRight.MoveTo(400, 120) v.Background.MoveTo(0, 0)
v.Engine.LoadingProgress += loadStep v.Engine.LoadingProgress += loadStep
v.DiabloLogoLeftBack = v.Engine.LoadSprite(ResourcePaths.Diablo2LogoBlackLeft, v.Engine.Palettes["units"]) }
v.DiabloLogoLeftBack.MoveTo(400, 120) {
v.Engine.LoadingProgress += loadStep v.TrademarkBackground = v.Engine.LoadSprite(ResourcePaths.TrademarkScreen, v.Engine.Palettes["sky"])
v.DiabloLogoRightBack = v.Engine.LoadSprite(ResourcePaths.Diablo2LogoBlackRight, v.Engine.Palettes["units"]) v.TrademarkBackground.MoveTo(0, 0)
v.DiabloLogoRightBack.MoveTo(400, 120) v.Engine.LoadingProgress += loadStep
v.Engine.LoadingProgress = 1.0 }
{
v.DiabloLogoLeft = v.Engine.LoadSprite(ResourcePaths.Diablo2LogoFireLeft, v.Engine.Palettes["units"])
v.DiabloLogoLeft.Blend = true
v.DiabloLogoLeft.Animate = true
v.DiabloLogoLeft.MoveTo(400, 120)
v.Engine.LoadingProgress += loadStep
}
{
v.DiabloLogoRight = v.Engine.LoadSprite(ResourcePaths.Diablo2LogoFireRight, v.Engine.Palettes["units"])
v.DiabloLogoRight.Blend = true
v.DiabloLogoRight.Animate = true
v.DiabloLogoRight.MoveTo(400, 120)
v.Engine.LoadingProgress += loadStep
}
{
v.DiabloLogoLeftBack = v.Engine.LoadSprite(ResourcePaths.Diablo2LogoBlackLeft, v.Engine.Palettes["units"])
v.DiabloLogoLeftBack.MoveTo(400, 120)
v.Engine.LoadingProgress += loadStep
}
{
v.DiabloLogoRightBack = v.Engine.LoadSprite(ResourcePaths.Diablo2LogoBlackRight, v.Engine.Palettes["units"])
v.DiabloLogoRightBack.MoveTo(400, 120)
v.Engine.LoadingProgress = 1.0
}
}() }()
} }
@ -76,6 +103,7 @@ func (v *MainMenu) Render(screen *ebiten.Image) {
if v.ShowTrademarkScreen { if v.ShowTrademarkScreen {
v.CopyrightLabel.Draw(screen) v.CopyrightLabel.Draw(screen)
v.CopyrightLabel2.Draw(screen)
} else { } else {
} }

View File

@ -2,6 +2,7 @@ package OpenDiablo2
import ( import (
"encoding/binary" "encoding/binary"
"image/color"
"time" "time"
"github.com/hajimehoshi/ebiten" "github.com/hajimehoshi/ebiten"
@ -16,6 +17,7 @@ type Sprite struct {
Blend bool Blend bool
LastFrameTime time.Time LastFrameTime time.Time
Animate bool Animate bool
ColorMod color.Color
} }
type SpriteFrame struct { type SpriteFrame struct {
@ -38,6 +40,7 @@ func CreateSprite(data []byte, palette Palette) Sprite {
Frame: 0, Frame: 0,
Direction: 0, Direction: 0,
Blend: false, Blend: false,
ColorMod: nil,
Directions: binary.LittleEndian.Uint32(data[16:20]), Directions: binary.LittleEndian.Uint32(data[16:20]),
FramesPerDirection: binary.LittleEndian.Uint32(data[20:24]), FramesPerDirection: binary.LittleEndian.Uint32(data[20:24]),
Animate: false, Animate: false,
@ -149,7 +152,9 @@ func (v *Sprite) Draw(target *ebiten.Image) {
if v.Blend { if v.Blend {
opts.CompositeMode = ebiten.CompositeModeLighter opts.CompositeMode = ebiten.CompositeModeLighter
} }
//opts.ColorM.ChangeHSV(0.0, 1.0, 0.9) if v.ColorMod != nil {
opts.ColorM = ColorToColorM(v.ColorMod)
}
target.DrawImage(frame.Image, opts) target.DrawImage(frame.Image, opts)
} }
@ -166,6 +171,12 @@ func (v *Sprite) DrawSegments(target *ebiten.Image, xSegments, ySegments, offset
float64(int32(v.X)+frame.OffsetX+xOffset), float64(int32(v.X)+frame.OffsetX+xOffset),
float64(int32(v.Y)+frame.OffsetY+yOffset), float64(int32(v.Y)+frame.OffsetY+yOffset),
) )
if v.Blend {
opts.CompositeMode = ebiten.CompositeModeLighter
}
if v.ColorMod != nil {
opts.ColorM = ColorToColorM(v.ColorMod)
}
target.DrawImage(frame.Image, opts) target.DrawImage(frame.Image, opts)
xOffset += int32(frame.Width) xOffset += int32(frame.Width)
biggestYOffset = MaxInt32(biggestYOffset, int32(frame.Height)) biggestYOffset = MaxInt32(biggestYOffset, int32(frame.Height))

View File

@ -1,23 +1,37 @@
package OpenDiablo2 package OpenDiablo2
import ( import (
"image/color"
"github.com/hajimehoshi/ebiten" "github.com/hajimehoshi/ebiten"
) )
type UILabelAlignment uint8
const (
UILabelAlignLeft UILabelAlignment = 0
UILabelAlignCenter UILabelAlignment = 1
UILabelAlignRight UILabelAlignment = 2
)
type UILabel struct { type UILabel struct {
text string text string
X int X int
Y int Y int
Width uint32 Width uint32
Height uint32 Height uint32
Alignment UILabelAlignment
font *MPQFont font *MPQFont
imageData *ebiten.Image imageData *ebiten.Image
ColorMod color.Color
} }
// CreateUILabel creates a new instance of a UI label // CreateUILabel creates a new instance of a UI label
func CreateUILabel(engine *Engine, font, palette string) *UILabel { func CreateUILabel(engine *Engine, font, palette string) *UILabel {
result := &UILabel{ result := &UILabel{
font: engine.GetFont(font, palette), Alignment: UILabelAlignLeft,
ColorMod: nil,
font: engine.GetFont(font, palette),
} }
return result return result
@ -30,7 +44,14 @@ func (v *UILabel) Draw(target *ebiten.Image) {
} }
v.cacheImage() v.cacheImage()
opts := &ebiten.DrawImageOptions{} opts := &ebiten.DrawImageOptions{}
opts.GeoM.Translate(float64(v.X), float64(v.Y))
if v.Alignment == UILabelAlignCenter {
opts.GeoM.Translate(float64(v.X-int(v.Width/2)), float64(v.Y))
} else if v.Alignment == UILabelAlignRight {
opts.GeoM.Translate(float64(v.X-int(v.Width)), float64(v.Y))
} else {
opts.GeoM.Translate(float64(v.X), float64(v.Y))
}
opts.CompositeMode = ebiten.CompositeModeSourceAtop opts.CompositeMode = ebiten.CompositeModeSourceAtop
opts.Filter = ebiten.FilterNearest opts.Filter = ebiten.FilterNearest
target.DrawImage(v.imageData, opts) target.DrawImage(v.imageData, opts)
@ -39,7 +60,7 @@ func (v *UILabel) Draw(target *ebiten.Image) {
func (v *UILabel) calculateSize() (uint32, uint32) { func (v *UILabel) calculateSize() (uint32, uint32) {
width := uint32(0) width := uint32(0)
height := uint32(0) height := uint32(0)
for ch := range v.text { for _, ch := range v.text {
metric := v.font.Metrics[uint8(ch)] metric := v.font.Metrics[uint8(ch)]
width += uint32(metric.Width) width += uint32(metric.Width)
height = Max(height, uint32(metric.Height)) height = Max(height, uint32(metric.Height))
@ -61,11 +82,12 @@ func (v *UILabel) cacheImage() {
v.Height = height v.Height = height
v.imageData, _ = ebiten.NewImage(int(width), int(height), ebiten.FilterNearest) v.imageData, _ = ebiten.NewImage(int(width), int(height), ebiten.FilterNearest)
x := uint32(0) x := uint32(0)
v.font.FontSprite.ColorMod = v.ColorMod
for _, ch := range v.text { for _, ch := range v.text {
char := uint8(ch) char := uint8(ch)
metric := v.font.Metrics[char] metric := v.font.Metrics[char]
v.font.FontSprite.Frame = char v.font.FontSprite.Frame = char
v.font.FontSprite.MoveTo(v.X+int(x), int(v.Height)) v.font.FontSprite.MoveTo(int(x), int(height))
v.font.FontSprite.Draw(v.imageData) v.font.FontSprite.Draw(v.imageData)
x += uint32(metric.Width) x += uint32(metric.Width)
} }