#include "LcdDisplay.h" LcdDisplay::LcdDisplay() { clear(); startTimerHz (30); } juce::Font LcdDisplay::getMonoFont() const { return juce::Font (juce::FontOptions { juce::Font::getDefaultMonospacedFontName(), (float) kCharH - 1.0f, juce::Font::plain }); } void LcdDisplay::rasterize (juce::uint32 c) { if (c >= 256 || glyphCache[c].cached) return; juce::Image img (juce::Image::ARGB, kCharW * 2, kCharH * 2, true); { juce::Graphics g (img); g.setColour (juce::Colours::white); g.setFont (getMonoFont()); juce::String s; s << (juce_wchar) c; g.drawText (s, img.getBounds().removeFromBottom (kCharH * 2 - 2), juce::Justification::centredBottom, false); } const float sx = (float) img.getWidth() / (float) kCharW; const float sy = (float) img.getHeight() / (float) kCharH; for (int row = 0; row < 8; ++row) glyphCache[c].rows[row] = 0; for (int row = 0; row < 8; ++row) { uint8_t bits = 0; for (int col = 0; col < 5; ++col) { int count = 0, total = 0; int px0 = (int) (col * sx); int py0 = (int) (row * sy); int px1 = juce::jmin ((int) ((col + 1) * sx), img.getWidth() - 1); int py1 = juce::jmin ((int) ((row + 1) * sy), img.getHeight() - 1); for (int y = py0; y <= py1; ++y) for (int x = px0; x <= px1; ++x) { total++; if (img.getPixelAt (x, y).getAlpha() > 127) count++; } if (total > 0 && count > total / 3) bits |= (uint8_t) (1u << (4 - col)); } glyphCache[c].rows[row] = bits; } glyphCache[c].cached = true; } uint8_t* LcdDisplay::getGlyph (juce::uint32 c) { if (c >= 256) c = '?'; if (! glyphCache[c].cached) rasterize (c); return glyphCache[c].rows; } void LcdDisplay::setLine (int row, const juce::String& s) { if (row >= 0 && row < kRows) lines[row] = s; } void LcdDisplay::showCursor (bool show) { cursorVisible = show; } void LcdDisplay::clear() { for (int r = 0; r < kRows; ++r) lines[r] = ""; cursorCol = 0; cursorRow = 0; cursorVisible = false; } void LcdDisplay::paint (juce::Graphics& g) { const float cw = (float) kCharW; const float ch = (float) kCharH; const float w = (float) getWidth(); const float h = (float) getHeight(); const juce::Colour bgCol (0xFF0D1A05); const juce::Colour gridCol (0xFF15260A); const juce::Colour pixOn (0xFF4AE038); const juce::Colour pixOnDim (0xFF1E3A12); const juce::Colour pixOff (0xFF0F1C07); g.fillAll (bgCol); g.setColour (gridCol); float totalW = (float) kCols * cw; float totalH = (float) kRows * ch; float ox = (w - totalW) / 2.0f; float oy = (h - totalH) / 2.0f; g.fillRect (ox - 1.0f, oy - 1.0f, totalW + 2.0f, totalH + 2.0f); bool blinkOn = cursorVisible && ((int) (juce::Time::getMillisecondCounterHiRes() / 400.0) % 2 == 0); for (int row = 0; row < kRows; ++row) { const juce::String& line = lines[row]; for (int col = 0; col < kCols; ++col) { juce_wchar ch2 = col < line.length() ? line [(size_t) col] : (juce_wchar) ' '; uint8_t* glyph = getGlyph ((juce::uint32) ch2); float cx = ox + (float) col * cw; float cy = oy + (float) row * ch; g.setColour (pixOff); g.fillRect (cx + 0.5f, cy + 0.5f, cw - 1.0f, ch - 1.0f); for (int gRow = 0; gRow < 8; ++gRow) { uint8_t bits = glyph[gRow]; if (bits == 0) continue; for (int gCol = 0; gCol < 5; ++gCol) { if (bits & (1u << (4 - gCol))) { float px = cx + 1.0f + (float) gCol * (cw - 2.0f) / 5.0f; float py = cy + 1.0f + (float) gRow * (ch - 2.0f) / 9.0f; float pw = (cw - 2.0f) / 5.0f - 0.5f; float ph = (ch - 2.0f) / 9.0f - 0.3f; g.setColour (pixOnDim); g.fillRect (px - 0.4f, py - 0.3f, pw + 0.8f, ph + 0.6f); g.setColour (pixOn); g.fillRect (px, py, pw, ph); } } } if (blinkOn && col == cursorCol && row == cursorRow) { g.setColour (pixOn); g.fillRect (cx + 1.0f, cy + ch - 3.0f, cw - 2.0f, 2.0f); } } } } void LcdDisplay::timerCallback() { repaint(); }