Vilket bildformat kan C läsa utan externa bibliotek?

Permalänk

Vilket bildformat kan C läsa utan externa bibliotek?

Jag håller på med bildanalys. Jag tänker läsa små bilder, oftast nedpixlade 8-bits bilder.
Här använder jag C för maximal prestanda. Men då kom jag på att C har inte stöd för att läsa bilder. Men C har stöd för att läsa filer.

Då tänkte jag att det måste finnas något format som C kan läsa? Typ något bildformat som är uppbyggt som en array.
Finns det några förlag?

Permalänk
Hedersmedlem

Tekniskt sett är det väl antingen inget alls eller alla, beroende på hur man ser saken.
BMP är enkelt nog att jag enkelt lyckats läsa in en fil, manipulera den och skriva tillbaka utan något bibliotek och därtill utan några direkta besvär.

PPM och Targa ska vara ännu enklare tydligen, enligt denna tråd:
https://stackoverflow.com/questions/16636311/what-is-the-simp...

Visa signatur

Asus ROG STRIX B550-F / Ryzen 5800X3D / 48 GB 3200 MHz CL14 / Asus TUF 3080 OC / WD SN850 1 TB, Kingston NV1 2 TB + NAS / Corsair RM650x V3 / Acer XB271HU (1440p165) / LG C1 55"
NAS: 6700K/16GB/Debian+ZFS | Backup (offsite): 9600K/16GB/Debian+ZFS

Permalänk
Skrivet av Thomas:

Tekniskt sett är det väl antingen inget alls eller alla, beroende på hur man ser saken.
BMP är enkelt nog att jag enkelt lyckats läsa in en fil, manipulera den och skriva tillbaka utan något bibliotek och därtill utan några direkta besvär.

PPM och Targa ska vara ännu enklare tydligen, enligt denna tråd:
https://stackoverflow.com/questions/16636311/what-is-the-simp...

Kan man läsa in BMP filer via Windows API?
Alltså jag ska läsa bilden också. Men jag vill bara se pixelvärderna och storleken. Inget mer.

Jag tror jag väljer: portable graymap format

Permalänk
Hedersmedlem

Det finns ju väldigt små header only-bibliotek också. Bara att ladda ned och inkludera:
https://github.com/vallentin/LoadBMP

Permalänk
Medlem

Läsa bilder är ju i sig inget problem - det är bara en mängd data.
Tolka och hantera bilder är svårare. C i sig har inget stöd för några specifika bild-format, precis som de flesta andra programmeringsspråk.

Men det finns ju bibliotek för att hantera olika bildformat, och för alla de vanligare bildformaten finns det garanterat minst ett, ofta flera, olika C-bibliotek för att hantera dem.

För många bildformat är det nog inte heller några större problem att skriva ihop lite egen kod för att hantera dem - beroende på exakt vad man vill göra.

Permalänk
Skrivet av Elgot:

Det finns ju väldigt små header only-bibliotek också. Bara att ladda ned och inkludera:
https://github.com/vallentin/LoadBMP

Fint bibliotek. Det verkar som att det finns buggar i biblioteket dock.

Permalänk
Skrivet av Erik_T:

Läsa bilder är ju i sig inget problem - det är bara en mängd data.
Tolka och hantera bilder är svårare. C i sig har inget stöd för några specifika bild-format, precis som de flesta andra programmeringsspråk.

Men det finns ju bibliotek för att hantera olika bildformat, och för alla de vanligare bildformaten finns det garanterat minst ett, ofta flera, olika C-bibliotek för att hantera dem.

För många bildformat är det nog inte heller några större problem att skriva ihop lite egen kod för att hantera dem - beroende på exakt vad man vill göra.

Med tanke på att jag programmerar i C89(och är väldigt strikt att jag inte använder C99 eller högre) och fokuserar på prestanda så tror jag att PGM passar mig bra.

PGM har bara 4 rader av information
P2 <- Första raden är vad det är för typ av fil
# feep.pgm <- En kommentar
24 7 <- Första siffran är antalet rader och andra siffran är antalet kolumner
15 <- Denna siffra visar maximala värdet som pixlarna kan ha. Så i detta fall kommer 255 vara max.

Under rad 4 så är det en stor matris av data.

Permalänk

Vad tror ni om denna C kod? Kan jag optimera den mera?

typedef struct { size_t width; size_t height; uint8_t max_gray_value; uint8_t* pixels; }PGM; PGM* pgm_read(const char file_path[]) { /* Check if file_path holds .pgm */ if (strstr(file_path, ".pgm") == NULL) { return NULL; } /* Open file */ FILE* file = fopen(file_path, "rb"); if (file == NULL) { return NULL; } /* Create image */ PGM* image = (PGM*)malloc(sizeof(PGM)); /* Get the magic e.g P2, P5 */ char line[256]; fgets(line, sizeof(line), file); /* Get the comment */ fgets(line, sizeof(line), file); /* Read height and width */ int w, h; fscanf(file, "%d %d", &w, &h); image->height = h; image->width = w; /* Read the maximum value for gray scale */ int s; fscanf(file, "%d", &s); image->max_gray_value = s; /* Allocate 2D array for the height */ const size_t hw = h * w; image->pixels = (uint8_t*)malloc(hw); /* Read every row */ size_t i; for (i = 0; i < hw; i++) { fscanf(file, "%d", &s); image->pixels[i] = s; } /* Everything went fine */ return image; } void pgm_free(PGM* image) { if (image) { free(image->pixels); free(image); } } void pgm_print(PGM* image) { if (image) { const size_t h = image->height; const size_t w = image->width; size_t i, j; uint8_t* pixels0 = image->pixels; for (i = 0; i < h; i++) { for (j = 0; j < w; j++) { printf("%i ", (*image->pixels++)); } printf("\n"); } image->pixels = pixels0; printf("\n"); } } P2 #feep.pgm 5 5 255 200 300 100 500 255 0 1 2 3 4 1 2 3 4 5 100 101 102 103 104 200 200 200 200 200 /* Read a PGM image */ PGM* image = pgm_read("feep.pgm"); /* Print PGM image */ pgm_print(image); /* Free PGM image */ pgm_free(image);

Permalänk
Hedersmedlem
Skrivet av heretic16:

/* Byte sizes for h (8 is sizeof(uint8_t*)) */ const size_t bytes_h = h * 8;

Här menar du nog 1 snarare än 8.

I det här fallet spelar det nog ingen större roll, men att ha ett stort kontinuerligt minnesblock (av storlek h*w) snarare än många små kan också vara effektivare.

Permalänk
Medlem
Skrivet av heretic16:

Vad tror ni om denna C kod? Kan jag optimera den mera?

Formatet du har valt (Plain PGM) är enkelt att läsa för en människa men det är hysteriskt ineffektivt, både vad gäller lagringsutrymme och hur mycket jobb det är att läsa in det och parsea det i ett program. Såhär beskriver skaparna det närbesläktade Plain PBM

Citat:

There is actually another version of the PBM format, even more simplistic, more lavishly wasteful of space than PBM, called Plain PBM. Plain PBM actually came first, but even its inventor couldn't stand its recklessly squanderous use of resources after a while and switched to what we now know as the regular PBM format.

Jag tror att du luras lite av att det är enkelt att läsa formatet med blotta ögat, det blir absolut inte lättare att parsea det programmatiskt. Du måste omvandla ASCII till de bytes de representerar, vilket är betydligt tyngre än att bara läsa in bytes och så är det klart. Jag vet inte vad det är du vill uppnå, men om du "fokuserar på prestanda" så är detta helt fel filformat för dig. Du måste ta ett binärt filformat för att ens komma i närheten av vad som kan kallas hög prestanda. Om det räcker för ditt ändamål eller inte är det ju bara att testa, dock.

En fotnot är att du verkar ha missförstått filformatet, din kod kommer inte kunna läsa en godtycklig Plain PGM-fil. Magic number (som för Plain PGM är P2) bör kontrolleras, du kan inte anta att det som i specen beskrivs som "whitespace" är en radbrytning, det finns ingenting som säger att andra raden alltid är en kommentar (eller ens att det finns en andra rad), osv. Om du själv producerar filerna är ju det inget problem, men du kommer som sagt inte kunna läsa in vilken Plain PGM som helst.

Permalänk
Skrivet av Elgot:

Här menar du nog 1 snarare än 8.

I det här fallet spelar det nog ingen större roll, men att ha ett stort kontinuerligt minnesblock (av storlek h*w) snarare än många små kan också vara effektivare.

Varje pekare har 8 bytes i storlek

Hur menar du?
Jag har alltid fått för mig att det är bättre att multiplicera två tal till ett, istället för att göra det i en for-loop.

Permalänk
Skrivet av SimpLar:

Formatet du har valt (Plain PGM) är enkelt att läsa för en människa men det är hysteriskt ineffektivt, både vad gäller lagringsutrymme och hur mycket jobb det är att läsa in det och parsea det i ett program. Såhär beskriver skaparna det närbesläktade Plain PBM

Jag tror att du luras lite av att det är enkelt att läsa formatet med blotta ögat, det blir absolut inte lättare att parsea det programmatiskt. Du måste omvandla ASCII till de bytes de representerar, vilket är betydligt tyngre än att bara läsa in bytes och så är det klart. Jag vet inte vad det är du vill uppnå, men om du "fokuserar på prestanda" så är detta helt fel filformat för dig. Du måste ta ett binärt filformat för att ens komma i närheten av vad som kan kallas hög prestanda. Om det räcker för ditt ändamål eller inte är det ju bara att testa, dock.

En fotnot är att du verkar ha missförstått filformatet, din kod kommer inte kunna läsa en godtycklig Plain PGM-fil. Magic number (som för Plain PGM är P2) bör kontrolleras, du kan inte anta att det som i specen beskrivs som "whitespace" är en radbrytning, det finns ingenting som säger att andra raden alltid är en kommentar (eller ens att det finns en andra rad), osv. Om du själv producerar filerna är ju det inget problem, men du kommer som sagt inte kunna läsa in vilken Plain PGM som helst.

Okej. Intressant.
Så hur läser jag en riktig PGM fil då? Är det alltså en vanlig datafil som jag läser in och kastar om uint8_t* datan till en struct? Typ som man gör med serialisering?

Jag tror jag har löst problemet

PGM* pgm_read(const char file_path[]) { /* Check if file_path holds .pgm */ if (strstr(file_path, ".pgm") == NULL) { return NULL; } /* Open file */ FILE* file = fopen(file_path, "rb"); if (file == NULL) { return NULL; } /* Create image */ PGM* image = (PGM*)malloc(sizeof(PGM)); /* Get the magic e.g P2 or P5 */ char line[256]; fgets(line, sizeof(line), file); /* Check if it's P5 */ bool isP5 = strstr(line, "P5") != NULL; /* Read the comments */ bool search_comment = true; while (search_comment) { fgets(line, sizeof(line), file); search_comment = line[0] == '#'; } /* Read height and width */ int w, h; sscanf(line, "%d %d", &w, &h); image->height = h; image->width = w; /* Read the maximum value for gray scale */ int s; fgets(line, sizeof(line), file); sscanf(line, "%d", &s); image->max_gray_value = s; /* Read the data */ size_t hw = h * w; image->pixels = (uint8_t*)malloc(hw); if (isP5) { fread(image->pixels, sizeof(uint8_t), hw, file); } else { size_t i; for (i = 0; i < hw; i++) { fscanf(file, "%d", &s); image->pixels[i] = s; } } /* Everything went fine */ return image; }

Permalänk
Hedersmedlem
Skrivet av heretic16:

Varje pekare har 8 bytes i storlek

Ah, du hade en stjärna där också. Dock får man i så fall vara uppmärksam på att det inte nödvändigtvis alltid är 8.

Skrivet av heretic16:

Hur menar du?
Jag har alltid fått för mig att det är bättre att multiplicera två tal till ett, istället för att göra det i en for-loop.

Om du allokerar en stor buffert kommer den ligga kontinuerligt i minnet, medan fält-av-pekare-strategin kan leda till att data är utspritt i minnet. I det senare fallet kommer det vara svårare att utnyttja cache effektivt (och man kan inte kopiera hela blocket med memcpy till exempel). Däremot kan man ju komma åt elementen med variabel[x][y] snarare än variabel[x*h+y] (eller hur man nu lägger upp det) vilket kanske ser trevligare ut.

Permalänk
Skrivet av Elgot:

Ah, du hade en stjärna där också. Dock får man i så fall vara uppmärksam på att det inte nödvändigtvis alltid är 8.

Om du allokerar en stor buffert kommer den ligga kontinuerligt i minnet, medan fält-av-pekare-strategin kan leda till att data är utspritt i minnet. I det senare fallet kommer det vara svårare att utnyttja cache effektivt (och man kan inte kopiera hela blocket med memcpy till exempel). Däremot kan man ju komma åt elementen med variabel[x][y] snarare än variabel[x*h+y] (eller hur man nu lägger upp det) vilket kanske ser trevligare ut.

Det kanske beror på vilken processor man programmerar på? 64-bit så är väll en pekarbyte alltid 8 bytes, medan på 32-bit så är det 4 bytes och för 16-bits processor (oh!!) så är det 2 bytes?

Jag avallokerar alltid och är mycket noga på detta
Det tar lång tid att skriva C kod. Speciellt när man ska hålla på med machine learning i C. Inte bara C...utan även C89

Permalänk
Medlem
Skrivet av heretic16:

Det kanske beror på vilken processor man programmerar på? 64-bit så är väll en pekarbyte alltid 8 bytes, medan på 32-bit så är det 4 bytes och för 16-bits processor (oh!!) så är det 2 bytes?

Oftast. Förutom när det inte är så.
I välskriven, portabel kod så behöver man inte veta storleken på olika typer, och man skall aldrig använda uint8_t, int32_t eller andra typer med fix bredd om man inte absolut måste ha variabler med exakt den storleken. Främst för att de inte behöver vara definierade i alla kompilatorer.

Permalänk
Skrivet av Erik_T:

Oftast. Förutom när det inte är så.
I välskriven, portabel kod så behöver man inte veta storleken på olika typer, och man skall aldrig använda uint8_t, int32_t eller andra typer med fix bredd om man inte absolut måste ha variabler med exakt den storleken. Främst för att de inte behöver vara definierade i alla kompilatorer.

Jag brukar använda mig av size_t ofta när jag inte vet storleken, men jag vet att storleken kommer ej vara negativ.
uint8_t använder jag bara om jag är säker att det handlar om bytes.
uint16_t och uint32_t använder jag bara om storleken kräver det. Men däremot brukar jag använda int32_t om jag inte vet storleken, men jag har behov utav -1 för errorhantering.

Nu brukar väll C89 knappt användas sedan Microsoft gick förnyade sin C-kompilator. Jag har för mig att det var inte alls för länge sedan man kunde skriva C99 kod i MSVC.

Jag har aldrig stött på kompilatorer som ej kan ta uint32_t eller uint16_t.

Permalänk
Medlem

Varför använda ett bildformat som inte har ett färdigt bibliotek? Ett av de vanligaste bildformaten på webben PNG har ett bibliotek skrivet i C89.
http://www.libpng.org/pub/png/libpng.html

Om jag skulle nämna prestanda så skulle jag slänga ut fopen/fgets och använda stat/open/read för att sedan orientera mig framåt med pekare för att se hur filen är skapt. Bara för att du har magic P2 innebär inte att filen är skapt som en sådan, vilket återspeglar sig i antalet "Vulnerability Warning" hos libpng ovan. Du lär inte slippa lindrigare undan om du inte parsar filen noggrant och tar hänsyn till alla oväntade varianter.

Permalänk
Medlem
Skrivet av heretic16:

Jag brukar använda mig av size_t ofta när jag inte vet storleken, men jag vet att storleken kommer ej vara negativ.
uint8_t använder jag bara om jag är säker att det handlar om bytes.
uint16_t och uint32_t använder jag bara om storleken kräver det. Men däremot brukar jag använda int32_t om jag inte vet storleken, men jag har behov utav -1 för errorhantering.

Nu brukar väll C89 knappt användas sedan Microsoft gick förnyade sin C-kompilator. Jag har för mig att det var inte alls för länge sedan man kunde skriva C99 kod i MSVC.

Jag har aldrig stött på kompilatorer som ej kan ta uint32_t eller uint16_t.

Du har alltså aldrig använt en strikt C89 kompilator. (uint8_t med vänner blev inte del av C standarden förrän med C99, och kräver att man inkluderar stdint.h för att de skall vara definierade om kompilatorn stöder dem.)
Du har också uppenbarligen inte använt en kompilator för en arkitektur med mer ovanlig ord-storlek (Som t.ex. 36-bitar datorer. Ovanliga idag, så det är inte så konstigt om du inte stött på någon, men de finns.)

Om du vill använda bytes så är char/unsigned char ett bättre alternativ. De finns garanterat i alla C kompilatorer, och är den minsta addresserbara minnesenheten i C. Dvs de är bytes, vilket inte nödvändigtvis innebär att de är 8 bitar.

Vet du inte storleken på något, så är oftast int/unsigned int det bästa valet av typ. Det skall vara den "naturliga" storleken på heltal för den arkitekturen man kompilerar för, och är därmed vanligtvis effektivast att använda för aritmetik.

size_t används normalt när man behöver beskriva storlek i minnet. För beräkningar kan det vara sub-optimalt.

Permalänk
Medlem
Skrivet av mc68000:

Varför använda ett bildformat som inte har ett färdigt bibliotek? Ett av de vanligaste bildformaten på webben PNG har ett bibliotek skrivet i C89.
http://www.libpng.org/pub/png/libpng.html

Om jag skulle nämna prestanda så skulle jag slänga ut fopen/fgets och använda stat/open/read för att sedan orientera mig framåt med pekare för att se hur filen är skapt. Bara för att du har magic P2 innebär inte att filen är skapt som en sådan, vilket återspeglar sig i antalet "Vulnerability Warning" hos libpng ovan. Du lär inte slippa lindrigare undan om du inte parsar filen noggrant och tar hänsyn till alla oväntade varianter.

stat/open/read är inte del av C standarden och är inte portabla. För att bara läsa filer så duger fopen/fgets utmärkt även ur prestanda-synvinkel.

Permalänk
Skrivet av Erik_T:

Du har alltså aldrig använt en strikt C89 kompilator. (uint8_t med vänner blev inte del av C standarden förrän med C99, och kräver att man inkluderar stdint.h för att de skall vara definierade om kompilatorn stöder dem.)
Du har också uppenbarligen inte använt en kompilator för en arkitektur med mer ovanlig ord-storlek (Som t.ex. 36-bitar datorer. Ovanliga idag, så det är inte så konstigt om du inte stött på någon, men de finns.)

Om du vill använda bytes så är char/unsigned char ett bättre alternativ. De finns garanterat i alla C kompilatorer, och är den minsta addresserbara minnesenheten i C. Dvs de är bytes, vilket inte nödvändigtvis innebär att de är 8 bitar.

Vet du inte storleken på något, så är oftast int/unsigned int det bästa valet av typ. Det skall vara den "naturliga" storleken på heltal för den arkitekturen man kompilerar för, och är därmed vanligtvis effektivast att använda för aritmetik.

size_t används normalt när man behöver beskriva storlek i minnet. För beräkningar kan det vara sub-optimalt.

Jag vet att uint8_t osv kom med C99.
Jag brukar använda typdef för att definiera dom så det passar C89.

/* In ANSI C (C89), the __STDC_VERSION__ is not defined */ #ifndef __STDC_VERSION__ #define __STDC_VERSION__ 199409L /* STDC version of C89 standard */ #endif #ifndef _MSC_VER #define NULL ((void *)0) #endif /* !_MSC_VER */ #if __STDC_VERSION__ < 199901L #define true 1 #define false 0 #endif /* !__STDC_VERSION___ */ #if __STDC_VERSION__ < 199901L typedef unsigned char uint8_t; typedef signed char int8_t; typedef unsigned short uint16_t; typedef signed short int16_t; typedef unsigned long uint32_t; typedef signed long int32_t; typedef unsigned long long size_t; typedef uint8_t bool; #else #include <stdbool.h> /* For bool datatype */ #include <stdint.h> /* For uint8_t, uint16_t and uint16_t */ #endif /* !__STDC_VERSION__ */

Jag tycker det blir så mycket text när man ska skriva "unsigned long long" osv. Bättre att man översätter dom till kortare ord.

Jag använder Eclipse CDT när jag vill kolla om min kod är strikt C89.

Permalänk
Medlem
Skrivet av heretic16:

Fint bibliotek. Det verkar som att det finns buggar i biblioteket dock.

Finns buggar i det, säkerligen, men om du tänker skriva själv kommer det nästan garanterat vara ännu mer buggar

Permalänk
Medlem
Skrivet av heretic16:

Jag vet att uint8_t osv kom med C99.
Jag brukar använda typdef för att definiera dom så det passar C89.

/* In ANSI C (C89), the __STDC_VERSION__ is not defined */ #ifndef __STDC_VERSION__ #define __STDC_VERSION__ 199409L /* STDC version of C89 standard */ #endif #ifndef _MSC_VER #define NULL ((void *)0) #endif /* !_MSC_VER */ #if __STDC_VERSION__ < 199901L #define true 1 #define false 0 #endif /* !__STDC_VERSION___ */ #if __STDC_VERSION__ < 199901L typedef unsigned char uint8_t; typedef signed char int8_t; typedef unsigned short uint16_t; typedef signed short int16_t; typedef unsigned long uint32_t; typedef signed long int32_t; typedef unsigned long long size_t; typedef uint8_t bool; #else #include <stdbool.h> /* For bool datatype */ #include <stdint.h> /* For uint8_t, uint16_t and uint16_t */ #endif /* !__STDC_VERSION__ */

Jag tycker det blir så mycket text när man ska skriva "unsigned long long" osv. Bättre att man översätter dom till kortare ord.

Jag använder Eclipse CDT när jag vill kolla om min kod är strikt C89.

Sådana där typedefs är väldigt maskinberoende. Du vet inte om short är 16 bitar, eller om long är exakt 32 bitar.
Att göra en egen typedef för size_t är rätt dumt. Dels så finns den redan definierad i C89/C90, och dels så är den nog så ofta mindre än 64 bitar. För övrigt så tillkom inte long long till C standarden förrän med C99, så att definiera size_t i termer av long long är väldigt bakvänt.

Visst, att skriva "unsigned long long" en massa gånger blir tjatigt, men det är så sällan man behöver det.
Skall man bara ha en heltals variabel och inte har några särskilda krav på den så duger "int" nästan alltid, och mycket kortare än så blir det inte.

Permalänk
Skrivet av Erik_T:

Sådana där typedefs är väldigt maskinberoende. Du vet inte om short är 16 bitar, eller om long är exakt 32 bitar.
Att göra en egen typedef för size_t är rätt dumt. Dels så finns den redan definierad i C89/C90, och dels så är den nog så ofta mindre än 64 bitar. För övrigt så tillkom inte long long till C standarden förrän med C99, så att definiera size_t i termer av long long är väldigt bakvänt.

Visst, att skriva "unsigned long long" en massa gånger blir tjatigt, men det är så sällan man behöver det.
Skall man bara ha en heltals variabel och inte har några särskilda krav på den så duger "int" nästan alltid, och mycket kortare än så blir det inte.

Fel av mig. Det ska vara "typedef unsigned long" för size_t.

När det kommer till C89 så antar jag att det går utmärkt att anta att unsigned long kan översättas till size_t. Sedan vad maximala värdet blir i verkligheten, ja det är upp till användaren. Jag vill bara garantera att använder man unsigned long så är det alltid större än unsigned int.

Sedan används väll aldrig hårdvara som är endast anpassat för C89 idag. C89 ser jag bara som en standard när man vill behålla bakåtkompabilitet då standarden stöds av ANSI, men den ändras aldrig.

Det betyder att har man en gång skrivit C89 kod, så kommer den alltid att fungera.
Jag håller på med matematik i C89 och matematiken förändras aldrig.

Permalänk
Skrivet av mc68000:

Varför använda ett bildformat som inte har ett färdigt bibliotek? Ett av de vanligaste bildformaten på webben PNG har ett bibliotek skrivet i C89.
http://www.libpng.org/pub/png/libpng.html

Om jag skulle nämna prestanda så skulle jag slänga ut fopen/fgets och använda stat/open/read för att sedan orientera mig framåt med pekare för att se hur filen är skapt. Bara för att du har magic P2 innebär inte att filen är skapt som en sådan, vilket återspeglar sig i antalet "Vulnerability Warning" hos libpng ovan. Du lär inte slippa lindrigare undan om du inte parsar filen noggrant och tar hänsyn till alla oväntade varianter.

Gud vilken grötig kod det är. Man ser verkligen att dom som har skrivit projektet verkar vara K&C kodare.

png_const_charp PNGAPI png_get_copyright(png_const_structrp png_ptr) { ..... }

För mig är PGM tillräckligt bra för att lösa mitt problem Jag behöver ju bara en lite C-snurra som kan läsa lite bilder bara.