From 01524fdb1ddd2773efa9d24f252d78218701cbb4 Mon Sep 17 00:00:00 2001 From: Tim Sarbin Date: Thu, 24 Oct 2019 19:13:30 -0400 Subject: [PATCH] Finished font rendering with color mod support. --- ColorConvert.go | 76 ++++++++++++++++++++++++++++++++++++++++++++ SceneMainMenu.go | 82 ++++++++++++++++++++++++++++++++---------------- Sprite.go | 13 +++++++- UILabel.go | 30 +++++++++++++++--- 4 files changed, 169 insertions(+), 32 deletions(-) create mode 100644 ColorConvert.go diff --git a/ColorConvert.go b/ColorConvert.go new file mode 100644 index 00000000..c0c537c1 --- /dev/null +++ b/ColorConvert.go @@ -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 +} diff --git a/SceneMainMenu.go b/SceneMainMenu.go index 77471ecd..3405fb30 100644 --- a/SceneMainMenu.go +++ b/SceneMainMenu.go @@ -1,6 +1,8 @@ package OpenDiablo2 import ( + "image/color" + "github.com/essial/OpenDiablo2/ResourcePaths" "github.com/hajimehoshi/ebiten" ) @@ -14,6 +16,7 @@ type MainMenu struct { DiabloLogoLeftBack Sprite DiabloLogoRightBack Sprite CopyrightLabel *UILabel + CopyrightLabel2 *UILabel ShowTrademarkScreen bool } @@ -28,34 +31,58 @@ func CreateMainMenu(engine *Engine) *MainMenu { func (v *MainMenu) Load() { go func() { - loadStep := 1.0 / 7.0 + loadStep := 1.0 / 8.0 v.Engine.LoadingProgress = 0 - v.CopyrightLabel = CreateUILabel(v.Engine, ResourcePaths.FontFormal11, "static") - v.CopyrightLabel.SetText("Hello, world!") - v.CopyrightLabel.MoveTo(0, 0) - v.Engine.LoadingProgress += loadStep - v.Background = v.Engine.LoadSprite(ResourcePaths.GameSelectScreen, v.Engine.Palettes["sky"]) - v.Background.MoveTo(0, 0) - 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.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 + { + v.CopyrightLabel = CreateUILabel(v.Engine, ResourcePaths.FontFormal12, "static") + v.CopyrightLabel.Alignment = UILabelAlignCenter + v.CopyrightLabel.SetText("Diablo 2 is © Copyright 2000-2016 Blizzard Entertainment") + v.CopyrightLabel.ColorMod = color.RGBA{188, 168, 140, 255} + v.CopyrightLabel.MoveTo(400, 500) + v.Engine.LoadingProgress += loadStep + } + { + v.CopyrightLabel2 = CreateUILabel(v.Engine, ResourcePaths.FontFormal12, "static") + v.CopyrightLabel2.Alignment = UILabelAlignCenter + v.CopyrightLabel2.SetText("All Rights Reserved.") + v.CopyrightLabel2.ColorMod = color.RGBA{188, 168, 140, 255} + v.CopyrightLabel2.MoveTo(400, 525) + v.Engine.LoadingProgress += loadStep + } + { + v.Background = v.Engine.LoadSprite(ResourcePaths.GameSelectScreen, v.Engine.Palettes["sky"]) + v.Background.MoveTo(0, 0) + 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.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 { v.CopyrightLabel.Draw(screen) + v.CopyrightLabel2.Draw(screen) } else { } diff --git a/Sprite.go b/Sprite.go index c99bc118..98f87921 100644 --- a/Sprite.go +++ b/Sprite.go @@ -2,6 +2,7 @@ package OpenDiablo2 import ( "encoding/binary" + "image/color" "time" "github.com/hajimehoshi/ebiten" @@ -16,6 +17,7 @@ type Sprite struct { Blend bool LastFrameTime time.Time Animate bool + ColorMod color.Color } type SpriteFrame struct { @@ -38,6 +40,7 @@ func CreateSprite(data []byte, palette Palette) Sprite { Frame: 0, Direction: 0, Blend: false, + ColorMod: nil, Directions: binary.LittleEndian.Uint32(data[16:20]), FramesPerDirection: binary.LittleEndian.Uint32(data[20:24]), Animate: false, @@ -149,7 +152,9 @@ func (v *Sprite) Draw(target *ebiten.Image) { if v.Blend { 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) } @@ -166,6 +171,12 @@ func (v *Sprite) DrawSegments(target *ebiten.Image, xSegments, ySegments, offset float64(int32(v.X)+frame.OffsetX+xOffset), 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) xOffset += int32(frame.Width) biggestYOffset = MaxInt32(biggestYOffset, int32(frame.Height)) diff --git a/UILabel.go b/UILabel.go index 2002c19b..162de468 100644 --- a/UILabel.go +++ b/UILabel.go @@ -1,23 +1,37 @@ package OpenDiablo2 import ( + "image/color" + "github.com/hajimehoshi/ebiten" ) +type UILabelAlignment uint8 + +const ( + UILabelAlignLeft UILabelAlignment = 0 + UILabelAlignCenter UILabelAlignment = 1 + UILabelAlignRight UILabelAlignment = 2 +) + type UILabel struct { text string X int Y int Width uint32 Height uint32 + Alignment UILabelAlignment font *MPQFont imageData *ebiten.Image + ColorMod color.Color } // CreateUILabel creates a new instance of a UI label func CreateUILabel(engine *Engine, font, palette string) *UILabel { result := &UILabel{ - font: engine.GetFont(font, palette), + Alignment: UILabelAlignLeft, + ColorMod: nil, + font: engine.GetFont(font, palette), } return result @@ -30,7 +44,14 @@ func (v *UILabel) Draw(target *ebiten.Image) { } v.cacheImage() 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.Filter = ebiten.FilterNearest target.DrawImage(v.imageData, opts) @@ -39,7 +60,7 @@ func (v *UILabel) Draw(target *ebiten.Image) { func (v *UILabel) calculateSize() (uint32, uint32) { width := uint32(0) height := uint32(0) - for ch := range v.text { + for _, ch := range v.text { metric := v.font.Metrics[uint8(ch)] width += uint32(metric.Width) height = Max(height, uint32(metric.Height)) @@ -61,11 +82,12 @@ func (v *UILabel) cacheImage() { v.Height = height v.imageData, _ = ebiten.NewImage(int(width), int(height), ebiten.FilterNearest) x := uint32(0) + v.font.FontSprite.ColorMod = v.ColorMod for _, ch := range v.text { char := uint8(ch) metric := v.font.Metrics[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) x += uint32(metric.Width) }