#
tokens: 5354/50000 2/88 files (page 2/2)
lines: off (toggle) GitHub
raw markdown copy
This is page 2 of 2. Use http://codebase.md/Zebbeni/ansizalizer?page={x} to view the full context.

# Directory Structure

```
├── .gitignore
├── ansizalizer
├── app
│   ├── adapt
│   │   └── generate.go
│   ├── export.go
│   ├── item.go
│   ├── model.go
│   ├── process
│   │   ├── ascii.go
│   │   ├── custom.go
│   │   ├── image.go
│   │   ├── renderer.go
│   │   └── unicode.go
│   ├── resize.go
│   ├── update.go
│   └── view.go
├── assets
│   └── palettes
│       ├── android-screenshot-editor.hex
│       ├── cascade-gb.hex
│       ├── dull-aquatic.hex
│       ├── florescence.hex
│       ├── gb-blue-steel.hex
│       ├── hama-beads-tub.hex
│       ├── kiwami64-v1.hex
│       └── yes.hex
├── controls
│   ├── browser
│   │   ├── item.go
│   │   ├── model.go
│   │   └── update.go
│   ├── export
│   │   ├── destination
│   │   │   ├── model.go
│   │   │   ├── update.go
│   │   │   └── view.go
│   │   ├── model.go
│   │   ├── source
│   │   │   ├── model.go
│   │   │   ├── update.go
│   │   │   └── view.go
│   │   ├── update.go
│   │   └── view.go
│   ├── menu
│   │   └── model.go
│   ├── model.go
│   ├── settings
│   │   ├── advanced
│   │   │   ├── dithering
│   │   │   │   ├── list.go
│   │   │   │   ├── model.go
│   │   │   │   ├── update.go
│   │   │   │   └── view.go
│   │   │   ├── model.go
│   │   │   ├── sampling
│   │   │   │   ├── const.go
│   │   │   │   ├── item.go
│   │   │   │   ├── model.go
│   │   │   │   └── update.go
│   │   │   ├── update.go
│   │   │   └── view.go
│   │   ├── characters
│   │   │   ├── init.go
│   │   │   ├── model.go
│   │   │   ├── tabs.go
│   │   │   ├── update.go
│   │   │   └── view.go
│   │   ├── colors
│   │   │   ├── model.go
│   │   │   ├── update.go
│   │   │   └── view.go
│   │   ├── item.go
│   │   ├── model.go
│   │   ├── palettes
│   │   │   ├── adaptive
│   │   │   │   ├── init.go
│   │   │   │   ├── model.go
│   │   │   │   ├── update.go
│   │   │   │   └── view.go
│   │   │   ├── loader
│   │   │   │   ├── item.go
│   │   │   │   ├── model.go
│   │   │   │   ├── values.go
│   │   │   │   └── view.go
│   │   │   ├── lospec
│   │   │   │   ├── init.go
│   │   │   │   ├── list.go
│   │   │   │   ├── model.go
│   │   │   │   ├── update.go
│   │   │   │   └── view.go
│   │   │   ├── matrix.go
│   │   │   ├── model.go
│   │   │   ├── update.go
│   │   │   └── view.go
│   │   ├── size
│   │   │   ├── init.go
│   │   │   ├── model.go
│   │   │   ├── update.go
│   │   │   └── view.go
│   │   ├── state.go
│   │   ├── update.go
│   │   └── view.go
│   ├── update.go
│   └── view.go
├── display
│   └── model.go
├── env
│   ├── os_darwin.go
│   ├── os_linux.go
│   └── os_windows.go
├── event
│   ├── command.go
│   └── keymap.go
├── global
│   └── file.go
├── go.mod
├── go.sum
├── images
│   └── characters
│       ├── char_001.png
│       ├── char_002.png
│       ├── char_003.png
│       ├── char_004.png
│       ├── char_005.png
│       ├── char_006.png
│       ├── char_007.png
│       ├── char_008.png
│       ├── char_009.png
│       ├── char_010.png
│       ├── char_011.png
│       ├── char_012.png
│       ├── char_013.png
│       ├── char_014.png
│       ├── char_015.png
│       ├── char_016.png
│       ├── char_017.png
│       ├── char_018.png
│       ├── char_019.png
│       ├── char_020.png
│       ├── char_021.png
│       ├── char_022.png
│       ├── char_023.png
│       ├── char_024.png
│       ├── char_025.png
│       ├── char_026.png
│       ├── char_027.png
│       └── char_028.png
├── LICENSE.md
├── main.go
├── palette
│   ├── model.go
│   └── view.go
├── README.md
├── style
│   ├── box.go
│   └── color.go
├── test_images
│   ├── dock.png
│   ├── mermaid.png
│   ├── mona_lisa.jpg
│   ├── planet.png
│   ├── robots.png
│   ├── sewer.png
│   └── throne.png
└── viewer
    ├── model.go
    └── update.go
```

# Files

--------------------------------------------------------------------------------
/app/update.go:
--------------------------------------------------------------------------------

```go
package app

import (
	"bufio"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"strings"

	"github.com/atotto/clipboard"
	tea "github.com/charmbracelet/bubbletea"

	"github.com/Zebbeni/ansizalizer/app/adapt"
	"github.com/Zebbeni/ansizalizer/app/process"
	"github.com/Zebbeni/ansizalizer/event"
)

func (m Model) handleStartRenderToViewCmd() (Model, tea.Cmd) {
	m.viewer.WaitingOnRender = true
	return m, m.processRenderToViewCmd
}

func (m Model) handleFinishRenderToViewMsg(msg event.FinishRenderToViewMsg) (Model, tea.Cmd) {
	// cut out early if the finished render is for a previously selected image
	if msg.FilePath != m.controls.FileBrowser.ActiveFile {
		return m, nil
	}

	var cmd tea.Cmd
	m.viewer, cmd = m.viewer.Update(msg)
	return m, cmd
}

func (m Model) processRenderToViewCmd() tea.Msg {
	imgString := process.RenderImageFile(m.controls.Settings, m.controls.FileBrowser.ActiveFile)
	colorsString := "true color"
	if m.controls.Settings.Colors.IsLimited() {
		palette := m.controls.Settings.Colors.GetCurrentPalette()
		colorsString = palette.Title()
	}
	return event.FinishRenderToViewMsg{FilePath: m.controls.FileBrowser.ActiveFile, ImgString: imgString, ColorsString: colorsString}
}

func (m Model) handleStartExportMsg(msg event.StartExportMsg) (Model, tea.Cmd) {
	if m.waitingOnExport {
		return m, nil
	}

	var exportQueue []exportJob
	var err error

	// build export queue
	if msg.IsDir {
		exportQueue, err = buildExportQueue(msg.SourcePath, msg.DestinationPath, msg.UseSubDirs)
		if err != nil {
			return m, event.BuildDisplayCmd(fmt.Sprintf("error exporting: %s", err))
		}
	} else {
		exportQueue = []exportJob{
			{
				sourcePath:      msg.SourcePath,
				destinationPath: msg.DestinationPath,
			},
		}
	}

	m.exportIndex = 0
	m.exportQueue = exportQueue
	m.waitingOnExport = true

	return m, tea.Batch(event.StartRenderToExportCmd, event.BuildDisplayCmd(fmt.Sprintf("%d jobs queued", len(exportQueue))))
}

func (m Model) handleRenderToExportMsg() (Model, tea.Cmd) {

	currentJob := m.exportQueue[m.exportIndex]

	// render image
	imgString := process.RenderImageFile(m.controls.Settings, currentJob.sourcePath)

	// save file
	file, err := os.Create(currentJob.destinationPath)
	if err != nil {
		return m, event.BuildDisplayCmd("error creating save file")
	}

	w := bufio.NewWriter(file)
	_, err = w.WriteString(imgString)
	if err != nil {
		return m, event.BuildDisplayCmd("error writing to save file")
	}

	m.exportIndex += 1
	displayMsg := fmt.Sprintf("%d/%d exports completed", m.exportIndex, len(m.exportQueue))
	displayCmd := event.BuildDisplayCmd(displayMsg)

	if m.exportIndex >= len(m.exportQueue) {
		m.waitingOnExport = false
		return m, displayCmd
	}

	return m, tea.Batch(event.StartRenderToExportCmd, displayCmd)
}

func (m Model) startExportingDir(msg event.StartExportMsg) (Model, tea.Cmd) {
	return m, event.BuildDisplayCmd(fmt.Sprintf("exporting %s", msg.SourcePath))
}

func (m Model) startExportingFile(msg event.StartExportMsg) (Model, tea.Cmd) {
	return m, event.BuildDisplayCmd(fmt.Sprintf("exporting %s", msg.SourcePath))
}

func (m Model) handleStartAdaptingMsg() (Model, tea.Cmd) {
	filename := m.controls.FileBrowser.ActiveFilename()
	message := fmt.Sprintf("generating palette from %s...", filename)
	return m, tea.Batch(event.BuildDisplayCmd(message), m.processAdaptingCmd)
}

func (m Model) handleFinishAdaptingMsg(msg event.FinishAdaptingMsg) (Model, tea.Cmd) {
	m.controls.Settings.Colors.PaletteControls.Adapter = m.controls.Settings.Colors.PaletteControls.Adapter.SetPalette(msg.Colors, msg.Name)
	return m, tea.Batch(event.StartRenderToViewCmd, event.BuildDisplayCmd("rendering..."))
}

type Foo struct {
	Bar string
}

func (m Model) handleLospecRequestMsg(msg event.LospecRequestMsg) (Model, tea.Cmd) {
	// make url request
	r, err := http.Get(msg.URL)
	if err != nil {
		return m, event.BuildDisplayCmd("error making lospec request")
	}
	defer r.Body.Close()

	body, err := io.ReadAll(r.Body)
	if err != nil {
		return m, event.BuildDisplayCmd("error reading lospec response")
	}

	// parse json and populate LospecResponseMsg
	data := new(event.LospecData)
	err = json.Unmarshal(body, &data)
	if err != nil {
		return m, event.BuildDisplayCmd("error decoding lospec request")
	}

	// build Data Cmd
	return m, event.BuildLospecResponseCmd(event.LospecResponseMsg{
		ID:   msg.ID,
		Page: msg.Page,
		Data: *data,
	})
}

func (m Model) handleLospecResponseMsg(msg event.LospecResponseMsg) (Model, tea.Cmd) {
	var cmd tea.Cmd
	m.controls.Settings.Colors.PaletteControls.Lospec, cmd = m.controls.Settings.Colors.PaletteControls.Lospec.Update(msg)
	return m, cmd
}

func (m Model) processAdaptingCmd() tea.Msg {
	colors, name := adapt.GeneratePalette(m.controls.Settings.Colors.PaletteControls.Adapter, m.controls.FileBrowser.ActiveFile)
	return event.FinishAdaptingMsg{
		Name:   name,
		Colors: colors,
	}
}

func (m Model) handleControlsUpdate(msg tea.Msg) (Model, tea.Cmd) {
	var cmd tea.Cmd
	m.controls, cmd = m.controls.Update(msg)
	return m, cmd
}

func (m Model) handleDisplayMsg(msg tea.Msg) (Model, tea.Cmd) {
	var cmd tea.Cmd
	m.display, cmd = m.display.Update(msg)
	return m, cmd
}

func (m Model) handleCopy() (Model, tea.Cmd) {
	if err := clipboard.WriteAll(m.viewer.View()); err != nil {
		return m, event.BuildDisplayCmd("Error copying to clipboard")
		// we should have a place in the UI where we display errors or processing messages,
		// and package our desired event to the user in a specific command
	}
	filename := m.controls.FileBrowser.ActiveFilename()
	name := strings.Split(filename, ".")[0] // strip extension
	return m, event.BuildDisplayCmd(fmt.Sprintf("copied %s to clipboard", name))
}

func (m Model) handleSave() (Model, tea.Cmd) {
	name := strings.Split(m.controls.FileBrowser.ActiveFilename(), ".")[0]
	filename := fmt.Sprintf("%s.ansi", name)
	file, err := os.Create(filename)
	if err != nil {
		return m, event.BuildDisplayCmd("error creating save file")
	}

	w := bufio.NewWriter(file)
	_, err = w.WriteString(m.viewer.View())
	if err != nil {
		return m, event.BuildDisplayCmd("error writing to save file")
	}

	return m, event.BuildDisplayCmd(fmt.Sprintf("saved to %s", filename))
}

```

--------------------------------------------------------------------------------
/app/process/renderer.go:
--------------------------------------------------------------------------------

```go
package process

import (
	"image/color"
	"math"

	"github.com/lucasb-eyer/go-colorful"

	"github.com/Zebbeni/ansizalizer/controls/settings"
	"github.com/Zebbeni/ansizalizer/controls/settings/characters"
)

type Renderer struct {
	Settings             settings.Model
	shadeLightBlockFuncs map[rune]blockFunc
	shadeMedBlockFuncs   map[rune]blockFunc
	shadeHeavyBlockFuncs map[rune]blockFunc
	quarterBlockFuncs    map[rune]blockFunc
	halfBlockFuncs       map[rune]blockFunc
	fullBlockFuncs       map[rune]blockFunc
}

func New(s settings.Model) Renderer {
	m := Renderer{
		Settings: s,
	}
	m.fullBlockFuncs = m.createFullBlockFuncs()
	m.halfBlockFuncs = m.createHalfBlockFuncs()
	m.quarterBlockFuncs = m.createQuarterBlockFuncs()
	m.shadeLightBlockFuncs = m.createShadeLightFuncs()
	m.shadeMedBlockFuncs = m.createShadeMedFuncs()
	m.shadeHeavyBlockFuncs = m.createShadeHeavyFuncs()
	return m
}

type blockFunc func(r1, r2, r3, r4 colorful.Color) (colorful.Color, colorful.Color, float64)

func (m Renderer) createQuarterBlockFuncs() map[rune]blockFunc {
	return map[rune]blockFunc{
		'▀': m.calcTop,
		'▐': m.calcRight,
		'▞': m.calcDiagonal,
		'▖': m.calcBotLeft,
		'▘': m.calcTopLeft,
		'▝': m.calcTopRight,
		'▗': m.calcBotRight,
	}
}
func (m Renderer) createHalfBlockFuncs() map[rune]blockFunc {
	return map[rune]blockFunc{
		'▀': m.calcTop,
	}
}

func (m Renderer) createFullBlockFuncs() map[rune]blockFunc {
	return map[rune]blockFunc{
		'█': m.calcFull,
	}
}

func (m Renderer) createShadeLightFuncs() map[rune]blockFunc {
	return map[rune]blockFunc{
		'░': m.calcHeavy,
	}
}

func (m Renderer) createShadeMedFuncs() map[rune]blockFunc {
	return map[rune]blockFunc{
		'▒': m.calcHeavy,
	}
}

func (m Renderer) createShadeHeavyFuncs() map[rune]blockFunc {
	return map[rune]blockFunc{
		'▓': m.calcHeavy,
	}
}

func (m Renderer) getLightDarkPaletted(light, dark colorful.Color) (colorful.Color, colorful.Color) {
	_, _, p := m.Settings.Colors.GetSelected()
	colors := p.Colors()

	index := colors.Index(dark)
	paletteDark := colors.Convert(dark)

	palette := make([]color.Color, len(colors))
	copy(palette, colors)

	paletteMinusDarkest := append(colors[:index], colors[index+1:]...)
	paletteLight := paletteMinusDarkest.Convert(light)

	light, _ = colorful.MakeColor(paletteLight)
	dark, _ = colorful.MakeColor(paletteDark)

	// swap light / dark if light is darker than dark
	lightBlackDist := light.DistanceLuv(black)
	darkBlackDist := dark.DistanceLuv(black)
	if darkBlackDist > lightBlackDist {
		temp := light
		light = dark
		dark = temp
	}

	return light, dark
}

func (m Renderer) getDarkestPaletted() colorful.Color {
	if !m.Settings.Colors.IsLimited() {
		return black
	}
	_, _, p := m.Settings.Colors.GetSelected()
	colors := p.Colors()
	darkest := colors.Convert(black)
	darkestConverted, _ := colorful.MakeColor(darkest)
	return darkestConverted
}

func (m Renderer) calcLight(r1, r2, r3, r4 colorful.Color) (colorful.Color, colorful.Color, float64) {
	if _, _, fgBg, _ := m.Settings.Characters.Selected(); fgBg == characters.OneColor {
		avg, dist := m.avgCol(r1, r2, r3, r4)
		return avg, colorful.Color{}, math.Min(1.0, math.Abs(dist-1))
	} else {
		_, dark := lightDark(r1, r2, r3, r4)
		avg := m.avgColTrue(r1, r2, r3, r4)

		if m.Settings.Colors.IsLimited() {
			avg, dark = m.getLightDarkPaletted(avg, dark)
		}

		dist := avg.DistanceLuv(black)
		return avg, dark, math.Min(1.0, math.Abs(dist))
	}
}

func (m Renderer) calcMed(r1, r2, r3, r4 colorful.Color) (colorful.Color, colorful.Color, float64) {
	if _, _, fgBg, _ := m.Settings.Characters.Selected(); fgBg == characters.OneColor {
		avg, dist := m.avgCol(r1, r2, r3, r4)
		return avg, colorful.Color{}, math.Min(1.0, math.Abs(dist-1))
	} else {
		_, dark := lightDark(r1, r2, r3, r4)
		avg := m.avgColTrue(r1, r2, r3, r4)

		if m.Settings.Colors.IsLimited() {
			avg, dark = m.getLightDarkPaletted(avg, dark)
		}

		dist := avg.DistanceLuv(black)
		return avg, dark, math.Min(1.0, math.Abs(dist-0.5))
	}
}

func (m Renderer) calcHeavy(r1, r2, r3, r4 colorful.Color) (colorful.Color, colorful.Color, float64) {
	if _, _, fgBg, _ := m.Settings.Characters.Selected(); fgBg == characters.OneColor {
		avg, dist := m.avgCol(r1, r2, r3, r4)
		return avg, colorful.Color{}, math.Min(1.0, math.Abs(dist-1))
	} else {
		_, dark := lightDark(r1, r2, r3, r4)
		avg := m.avgColTrue(r1, r2, r3, r4)

		if m.Settings.Colors.IsLimited() {
			avg, dark = m.getLightDarkPaletted(avg, dark)
		}

		dist := avg.DistanceLuv(black)
		return avg, dark, math.Min(1.0, math.Abs(dist-1))
	}
}

func (m Renderer) calcFull(r1, r2, r3, r4 colorful.Color) (colorful.Color, colorful.Color, float64) {
	if _, _, fgBg, _ := m.Settings.Characters.Selected(); fgBg == characters.OneColor {
		avg, _ := m.avgCol(r1, r2, r3, r4)
		return avg, colorful.Color{}, 1.0
	} else {
		_, dark := lightDark(r1, r2, r3, r4)
		avg := m.avgColTrue(r1, r2, r3, r4)

		if m.Settings.Colors.IsLimited() {
			avg, dark = m.getLightDarkPaletted(avg, dark)
		}

		dist := avg.DistanceLuv(black)
		return avg, dark, math.Min(1.0, math.Abs(dist-1))
	}
}

func (m Renderer) calcTop(r1, r2, r3, r4 colorful.Color) (colorful.Color, colorful.Color, float64) {
	if r1.R == 0 && r1.G == 0 && r1.B == 0 && (r3.R != 0 || r3.G != 0 || r3.B != 0) {
		r1.R = r1.G
	}
	fg, fDist := m.avgCol(r1, r2)
	bg, bDist := m.avgCol(r3, r4)
	return fg, bg, fDist + bDist
}

func (m Renderer) calcRight(r1, r2, r3, r4 colorful.Color) (colorful.Color, colorful.Color, float64) {
	fg, fDist := m.avgCol(r2, r4)
	bg, bDist := m.avgCol(r1, r3)
	return fg, bg, fDist + bDist
}

func (m Renderer) calcDiagonal(r1, r2, r3, r4 colorful.Color) (colorful.Color, colorful.Color, float64) {
	fg, fDist := m.avgCol(r2, r3)
	bg, bDist := m.avgCol(r1, r4)
	return fg, bg, fDist + bDist
}

func (m Renderer) calcBotLeft(r1, r2, r3, r4 colorful.Color) (colorful.Color, colorful.Color, float64) {
	fg, fDist := m.avgCol(r3)
	bg, bDist := m.avgCol(r1, r2, r4)
	return fg, bg, fDist + bDist
}

func (m Renderer) calcTopLeft(r1, r2, r3, r4 colorful.Color) (colorful.Color, colorful.Color, float64) {
	fg, fDist := m.avgCol(r1)
	bg, bDist := m.avgCol(r2, r3, r4)
	return fg, bg, fDist + bDist
}

func (m Renderer) calcTopRight(r1, r2, r3, r4 colorful.Color) (colorful.Color, colorful.Color, float64) {
	fg, fDist := m.avgCol(r2)
	bg, bDist := m.avgCol(r1, r3, r4)
	return fg, bg, fDist + bDist
}

func (m Renderer) calcBotRight(r1, r2, r3, r4 colorful.Color) (colorful.Color, colorful.Color, float64) {
	fg, fDist := m.avgCol(r4)
	bg, bDist := m.avgCol(r1, r2, r3)
	return fg, bg, fDist + bDist
}

```
Page 2/2FirstPrevNextLast