Browse Source

add AirPlay artwork

Philippe G 3 years ago
parent
commit
a16a1678e5

+ 6 - 5
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 ); }

+ 1 - 0
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 );

+ 9 - 9
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;
         }

+ 1 - 1
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;
 	

+ 6 - 6
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';
 	}

+ 1 - 1
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 ) {

+ 2 - 4
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 ) {

+ 30 - 0
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;

+ 1 - 0
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);

+ 1 - 1
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);

+ 3 - 0
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);

+ 7 - 2
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);

+ 1 - 1
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 ;