diff --git a/components/display/core/gds.c b/components/display/core/gds.c index 63e32861..fc1d8d3d 100644 --- a/components/display/core/gds.c +++ b/components/display/core/gds.c @@ -235,12 +235,13 @@ void GDS_SetContrast( struct GDS_Device* Device, uint8_t Contrast ) { ledc_update_duty( LEDC_HIGH_SPEED_MODE, Device->Backlight.Channel ); } } - + void GDS_SetLayout( struct GDS_Device* Device, bool HFlip, bool VFlip, bool Rotate ) { if (Device->SetLayout) Device->SetLayout( Device, HFlip, VFlip, Rotate ); } void GDS_SetDirty( struct GDS_Device* Device ) { Device->Dirty = true; } -int GDS_GetWidth( struct GDS_Device* Device ) { return Device ? Device->Width : 0; } -int GDS_GetHeight( struct GDS_Device* Device ) { return Device ? Device->Height : 0; } -int GDS_GetDepth( struct GDS_Device* Device ) { return Device ? Device->Depth : 0; } -int GDS_GetMode( struct GDS_Device* Device ) { return Device ? Device->Mode : 0; } +int GDS_GetWidth( struct GDS_Device* Device ) { return Device ? Device->Width : 0; } +void GDS_SetTextWidth( struct GDS_Device* Device, int TextWidth ) { Device->TextWidth = Device && TextWidth && TextWidth < Device->Width ? TextWidth : Device->Width; } +int GDS_GetHeight( struct GDS_Device* Device ) { return Device ? Device->Height : 0; } +int GDS_GetDepth( struct GDS_Device* Device ) { return Device ? Device->Depth : 0; } +int GDS_GetMode( struct GDS_Device* Device ) { return Device ? Device->Mode : 0; } void GDS_DisplayOn( struct GDS_Device* Device ) { if (Device->DisplayOn) Device->DisplayOn( Device ); } void GDS_DisplayOff( struct GDS_Device* Device ) { if (Device->DisplayOff) Device->DisplayOff( Device ); } \ No newline at end of file diff --git a/components/display/core/gds.h b/components/display/core/gds.h index d79ade04..fa782329 100644 --- a/components/display/core/gds.h +++ b/components/display/core/gds.h @@ -38,6 +38,7 @@ void GDS_Update( struct GDS_Device* Device ); void GDS_SetLayout( struct GDS_Device* Device, bool HFlip, bool VFlip, bool Rotate ); void GDS_SetDirty( struct GDS_Device* Device ); int GDS_GetWidth( struct GDS_Device* Device ); +void GDS_SetTextWidth( struct GDS_Device* Device, int TextWidth ); int GDS_GetHeight( struct GDS_Device* Device ); int GDS_GetDepth( struct GDS_Device* Device ); int GDS_GetMode( struct GDS_Device* Device ); diff --git a/components/display/core/gds_font.c b/components/display/core/gds_font.c index 2ea38872..65c1a08e 100644 --- a/components/display/core/gds_font.c +++ b/components/display/core/gds_font.c @@ -73,13 +73,13 @@ void GDS_FontDrawChar( struct GDS_Device* Device, char Character, int x, int y, CharStartY+= OffsetY; /* Do not attempt to draw if this character is entirely offscreen */ - if ( CharEndX < 0 || CharStartX >= Device->Width || CharEndY < 0 || CharStartY >= Device->Height ) { + if ( CharEndX < 0 || CharStartX >= Device->TextWidth || CharEndY < 0 || CharStartY >= Device->Height ) { ClipDebug( x, y ); return; } /* Do not attempt to draw past the end of the screen */ - CharEndX = ( CharEndX >= Device->Width ) ? Device->Width - 1 : CharEndX; + CharEndX = ( CharEndX >= Device->TextWidth ) ? Device->TextWidth - 1 : CharEndX; CharEndY = ( CharEndY >= Device->Height ) ? Device->Height - 1 : CharEndY; Device->Dirty = true; @@ -146,7 +146,7 @@ int GDS_FontGetCharWidth( struct GDS_Device* Display, char Character ) { } int GDS_FontGetMaxCharsPerRow( struct GDS_Device* Display ) { - return Display->Width / Display->Font->Width; + return Display->TextWidth / Display->Font->Width; } int GDS_FontGetMaxCharsPerColumn( struct GDS_Device* Display ) { @@ -210,7 +210,7 @@ void GDS_FontGetAnchoredStringCoords( struct GDS_Device* Display, int* OutX, int switch ( Anchor ) { case TextAnchor_East: { *OutY = ( Display->Height / 2 ) - ( StringHeight / 2 ); - *OutX = ( Display->Width - StringWidth ); + *OutX = ( Display->TextWidth - StringWidth ); break; } @@ -221,19 +221,19 @@ void GDS_FontGetAnchoredStringCoords( struct GDS_Device* Display, int* OutX, int break; } case TextAnchor_North: { - *OutX = ( Display->Width / 2 ) - ( StringWidth / 2 ); + *OutX = ( Display->TextWidth / 2 ) - ( StringWidth / 2 ); *OutY = 0; break; } case TextAnchor_South: { - *OutX = ( Display->Width / 2 ) - ( StringWidth / 2 ); + *OutX = ( Display->TextWidth / 2 ) - ( StringWidth / 2 ); *OutY = ( Display->Height - StringHeight ); break; } case TextAnchor_NorthEast: { - *OutX = ( Display->Width - StringWidth ); + *OutX = ( Display->TextWidth - StringWidth ); *OutY = 0; break; @@ -246,7 +246,7 @@ void GDS_FontGetAnchoredStringCoords( struct GDS_Device* Display, int* OutX, int } case TextAnchor_SouthEast: { *OutY = ( Display->Height - StringHeight ); - *OutX = ( Display->Width - StringWidth ); + *OutX = ( Display->TextWidth - StringWidth ); break; } @@ -258,7 +258,7 @@ void GDS_FontGetAnchoredStringCoords( struct GDS_Device* Display, int* OutX, int } case TextAnchor_Center: { *OutY = ( Display->Height / 2 ) - ( StringHeight / 2 ); - *OutX = ( Display->Width / 2 ) - ( StringWidth / 2 ); + *OutX = ( Display->TextWidth / 2 ) - ( StringWidth / 2 ); break; } diff --git a/components/display/core/gds_private.h b/components/display/core/gds_private.h index 6b098ebe..a010836e 100644 --- a/components/display/core/gds_private.h +++ b/components/display/core/gds_private.h @@ -95,7 +95,7 @@ struct GDS_Device { const struct GDS_FontDef* Font; } Lines[MAX_LINES]; - uint16_t Width; + uint16_t Width, TextWidth; uint16_t Height; uint8_t Depth, Mode; diff --git a/components/display/core/gds_text.c b/components/display/core/gds_text.c index d0420564..89794e10 100644 --- a/components/display/core/gds_text.c +++ b/components/display/core/gds_text.c @@ -99,13 +99,13 @@ bool GDS_TextLine(struct GDS_Device* Device, int N, int Pos, int Attr, char *Tex Width = GDS_FontMeasureString( Device, Text ); // adjusting position, erase only EoL for rigth-justified - if (Pos == GDS_TEXT_RIGHT) X = Device->Width - Width - 1; - else if (Pos == GDS_TEXT_CENTER) X = (Device->Width - Width) / 2; + if (Pos == GDS_TEXT_RIGHT) X = Device->TextWidth - Width - 1; + else if (Pos == GDS_TEXT_CENTER) X = (Device->TextWidth - Width) / 2; // erase if requested if (Attr & GDS_TEXT_CLEAR) { int Y_min = max(0, Device->Lines[N].Y), Y_max = max(0, Device->Lines[N].Y + Device->Lines[N].Font->Height); - for (int c = (Attr & GDS_TEXT_CLEAR_EOL) ? X : 0; c < Device->Width; c++) + for (int c = (Attr & GDS_TEXT_CLEAR_EOL) ? X : 0; c < Device->TextWidth; c++) for (int y = Y_min; y < Y_max; y++) DrawPixelFast( Device, c, y, GDS_COLOR_BLACK ); } @@ -118,7 +118,7 @@ bool GDS_TextLine(struct GDS_Device* Device, int N, int Pos, int Attr, char *Tex Device->Dirty = true; if (Attr & GDS_TEXT_UPDATE) GDS_Update( Device ); - return Width + X < Device->Width; + return Width + X < Device->TextWidth; } /**************************************************************************************** @@ -145,7 +145,7 @@ int GDS_TextStretch(struct GDS_Device* Device, int N, char *String, int Max) { // we might already fit GDS_SetFont( Device, Device->Lines[N].Font ); - if (GDS_FontMeasureString( Device, String ) <= Device->Width) return 0; + if (GDS_FontMeasureString( Device, String ) <= Device->TextWidth) return 0; // add some space for better visual strncat(String, Space, Max-Len); @@ -156,7 +156,7 @@ int GDS_TextStretch(struct GDS_Device* Device, int N, char *String, int Max) { Boundary = GDS_FontMeasureString( Device, String ); // add a full display width - while (Len < Max && GDS_FontMeasureString( Device, String ) - Boundary < Device->Width) { + while (Len < Max && GDS_FontMeasureString( Device, String ) - Boundary < Device->TextWidth) { String[Len++] = String[Extra++]; String[Len] = '\0'; } diff --git a/components/display/core/ifaces/default_if_i2c.c b/components/display/core/ifaces/default_if_i2c.c index 28fd2347..22f5934e 100644 --- a/components/display/core/ifaces/default_if_i2c.c +++ b/components/display/core/ifaces/default_if_i2c.c @@ -75,7 +75,7 @@ bool GDS_I2CAttachDevice( struct GDS_Device* Device, int Width, int Height, int Device->RSTPin = RSTPin; Device->Backlight.Pin = BacklightPin; Device->IF = GDS_IF_I2C; - Device->Width = Width; + Device->Width = Device->TextWidth = Width; Device->Height = Height; if ( RSTPin >= 0 ) { diff --git a/components/display/core/ifaces/default_if_spi.c b/components/display/core/ifaces/default_if_spi.c index 968ce51c..1f047584 100644 --- a/components/display/core/ifaces/default_if_spi.c +++ b/components/display/core/ifaces/default_if_spi.c @@ -35,7 +35,7 @@ bool GDS_SPIInit( int SPI, int DC ) { } bool GDS_SPIAttachDevice( struct GDS_Device* Device, int Width, int Height, int CSPin, int RSTPin, int BackLightPin, int Speed ) { - spi_device_interface_config_t SPIDeviceConfig; + spi_device_interface_config_t SPIDeviceConfig = { }; spi_device_handle_t SPIDevice; NullCheck( Device, return false ); @@ -45,8 +45,6 @@ bool GDS_SPIAttachDevice( struct GDS_Device* Device, int Width, int Height, int ESP_ERROR_CHECK_NONFATAL( gpio_set_level( CSPin, 0 ), return false ); } - memset( &SPIDeviceConfig, 0, sizeof( spi_device_interface_config_t ) ); - SPIDeviceConfig.clock_speed_hz = Speed > 0 ? Speed : SPI_MASTER_FREQ_8M; SPIDeviceConfig.spics_io_num = CSPin; SPIDeviceConfig.queue_size = 1; @@ -63,7 +61,7 @@ bool GDS_SPIAttachDevice( struct GDS_Device* Device, int Width, int Height, int Device->CSPin = CSPin; Device->Backlight.Pin = BackLightPin; Device->IF = GDS_IF_SPI; - Device->Width = Width; + Device->Width = Device->TextWidth = Width; Device->Height = Height; if ( RSTPin >= 0 ) { diff --git a/components/display/display.c b/components/display/display.c index 192c834a..8d5d7e9c 100644 --- a/components/display/display.c +++ b/components/display/display.c @@ -20,6 +20,7 @@ #include "gds_draw.h" #include "gds_text.h" #include "gds_font.h" +#include "gds_image.h" static const char *TAG = "display"; @@ -30,6 +31,7 @@ static const char *TAG = "display"; #define SCROLLABLE_SIZE 384 #define HEADER_SIZE 64 #define DEFAULT_SLEEP 3600 +#define ARTWORK_BORDER 1 static EXT_RAM_ATTR struct { TaskHandle_t task; @@ -47,6 +49,10 @@ static EXT_RAM_ATTR struct { char string[8]; // H:MM:SS bool visible; } duration; + struct { + bool enable, fit; + int offset; + } artwork; TickType_t tick; } displayer; @@ -147,6 +153,14 @@ void display_init(char *welcome) { GDS_TextSetFontAuto(display, 2, GDS_FONT_LINE_2, -3); displayer.metadata_config = config_alloc_get(NVS_TYPE_STR, "metadata_config"); + + // leave room for artwork is display is horizontal-style + if (strcasestr(displayer.metadata_config, "artwork")) { + displayer.artwork.enable = true; + displayer.artwork.fit = true; + if (height <= 64 && width > height * 2) displayer.artwork.offset = width - height - ARTWORK_BORDER; + PARSE_PARAM(displayer.metadata_config, "artwork", ':', displayer.artwork.fit); + } } free(config); @@ -238,6 +252,19 @@ static void displayer_task(void *args) { } } + +/**************************************************************************************** + * + */ +void displayer_artwork(uint8_t *data) { + if (!displayer.artwork.enable) return; + + int x = displayer.artwork.offset ? displayer.artwork.offset + ARTWORK_BORDER : 0; + int y = x ? 0 : 32; + GDS_ClearWindow(display, x, y, -1, -1, GDS_COLOR_BLACK); + if (data) GDS_DrawJPEG(display, data, x, y, GDS_IMAGE_CENTER | (displayer.artwork.fit ? GDS_IMAGE_FIT : 0)); +} + /**************************************************************************************** * */ @@ -378,6 +405,7 @@ void displayer_control(enum displayer_cmd_e cmd, ...) { switch(cmd) { case DISPLAYER_ACTIVATE: { char *header = va_arg(args, char*); + bool artwork = va_arg(args, int); strncpy(displayer.header, header, HEADER_SIZE); displayer.header[HEADER_SIZE] = '\0'; displayer.state = DISPLAYER_ACTIVE; @@ -388,6 +416,7 @@ void displayer_control(enum displayer_cmd_e cmd, ...) { displayer.duration.visible = false; displayer.offset = displayer.boundary = 0; display_bus(&displayer, DISPLAY_BUS_TAKE); + if (artwork) GDS_SetTextWidth(display, displayer.artwork.offset); vTaskResume(displayer.task); break; } @@ -398,6 +427,7 @@ void displayer_control(enum displayer_cmd_e cmd, ...) { break; case DISPLAYER_SHUTDOWN: // let the task self-suspend (we might be doing i2c_write) + GDS_SetTextWidth(display, 0); displayer.state = DISPLAYER_DOWN; display_bus(&displayer, DISPLAY_BUS_GIVE); break; diff --git a/components/display/display.h b/components/display/display.h index 9c945555..ebb857e7 100644 --- a/components/display/display.h +++ b/components/display/display.h @@ -38,5 +38,6 @@ bool display_is_valid_driver(const char * driver); void displayer_scroll(char *string, int speed, int pause); void displayer_control(enum displayer_cmd_e cmd, ...); void displayer_metadata(char *artist, char *album, char *title); +void displayer_artwork(uint8_t *data); void displayer_timer(enum displayer_time_e mode, int elapsed, int duration); char * display_get_supported_drivers(void); diff --git a/components/driver_bt/bt_app_sink.c b/components/driver_bt/bt_app_sink.c index a0945ccc..4036108d 100644 --- a/components/driver_bt/bt_app_sink.c +++ b/components/driver_bt/bt_app_sink.c @@ -172,7 +172,7 @@ static bool cmd_handler(bt_sink_cmd_t cmd, ...) { // now handle events for display switch(cmd) { case BT_SINK_AUDIO_STARTED: - displayer_control(DISPLAYER_ACTIVATE, "BLUETOOTH"); + displayer_control(DISPLAYER_ACTIVATE, "BLUETOOTH", false); break; case BT_SINK_AUDIO_STOPPED: displayer_control(DISPLAYER_SUSPEND); diff --git a/components/raop/raop.c b/components/raop/raop.c index 53679a78..82f5e8b4 100644 --- a/components/raop/raop.c +++ b/components/raop/raop.c @@ -628,6 +628,9 @@ static bool handle_rtsp(raop_ctx_t *ctx, int sock) success = ctx->cmd_cb(RAOP_METADATA, metadata.artist, metadata.album, metadata.title); free_metadata(&metadata); } + } else if (body && ((p = kd_lookup(headers, "Content-Type")) != NULL) && strcasestr(p, "image/jpeg")) { + LOG_INFO("[%p]: received JPEG image of %d bytes", ctx, len); + ctx->cmd_cb(RAOP_ARTWORK, body, len); } else { char *dump = kd_dump(headers); LOG_INFO("Unhandled SET PARAMETER\n%s", dump); diff --git a/components/raop/raop_sink.c b/components/raop/raop_sink.c index 3feb5f39..f37d259a 100644 --- a/components/raop/raop_sink.c +++ b/components/raop/raop_sink.c @@ -17,7 +17,6 @@ #include "audio_controls.h" #include "display.h" #include "accessors.h" - #include "log_util.h" #include "trace.h" @@ -115,7 +114,7 @@ static bool cmd_handler(raop_event_t event, ...) { switch(event) { case RAOP_SETUP: actrls_set(controls, false, NULL, actrls_ir_action); - displayer_control(DISPLAYER_ACTIVATE, "AIRPLAY"); + displayer_control(DISPLAYER_ACTIVATE, "AIRPLAY", true); break; case RAOP_PLAY: displayer_control(DISPLAYER_TIMER_RUN); @@ -130,8 +129,14 @@ static bool cmd_handler(raop_event_t event, ...) { case RAOP_METADATA: { char *artist = va_arg(args, char*), *album = va_arg(args, char*), *title = va_arg(args, char*); displayer_metadata(artist, album, title); + displayer_artwork(NULL); break; } + case RAOP_ARTWORK: { + uint8_t *data = va_arg(args, uint8_t*); + displayer_artwork(data); + break; + } case RAOP_PROGRESS: { int elapsed = va_arg(args, int), duration = va_arg(args, int); displayer_timer(DISPLAYER_ELAPSED, elapsed, duration); diff --git a/components/raop/raop_sink.h b/components/raop/raop_sink.h index a3cb87d4..4f6c5dea 100644 --- a/components/raop/raop_sink.h +++ b/components/raop/raop_sink.h @@ -14,7 +14,7 @@ #define RAOP_SAMPLE_RATE 44100 -typedef enum { RAOP_SETUP, RAOP_STREAM, RAOP_PLAY, RAOP_FLUSH, RAOP_METADATA, RAOP_PROGRESS, RAOP_PAUSE, RAOP_STOP, +typedef enum { RAOP_SETUP, RAOP_STREAM, RAOP_PLAY, RAOP_FLUSH, RAOP_METADATA, RAOP_ARTWORK, RAOP_PROGRESS, RAOP_PAUSE, RAOP_STOP, RAOP_VOLUME, RAOP_TIMING, RAOP_PREV, RAOP_NEXT, RAOP_REW, RAOP_FWD, RAOP_VOLUME_UP, RAOP_VOLUME_DOWN, RAOP_RESUME, RAOP_TOGGLE } raop_event_t ;