HSV->RGB-Methode: Speicherplatz optimieren

Gast #1498625
Lesenswert?

Hallo,

ich benutze eine Funktion, welche HSV-Koordinaten in RGB-Koordinaten 
umrechnet. Leider ist diese Funktion 378 Byte groß, was auf einem 
ATtiny13 deutlich zu viel ist (damit der Rest des Programms auch noch 
Platz hat).

Daher würde ich diese Methode optimieren, finde aber keinen 
Optimierungsspielraum mehr:
1
void setColorHSV(char hue, char saturation, char value)
2
{
3
    unsigned char i, f;
4
    unsigned int  p, q, t;
5
    unsigned char red = 0, green = 0, blue = 0;
6

7
    if (saturation == 0)  // Farbsättigung ist Null -> Grau
8
    {
9
        red = green = blue = 0;
10
    }
11
    else                  // Farbsättigung ist nicht Null -> Einteilen in 60°-Winkel des Farbtons
12
    {
13
        i = hue / 43;
14
        f = hue % 43;
15
        p = (value * (255 - saturation)) / 256;
16
        q = (value * ((10710 - (saturation * f)) / 42)) / 256;
17
        t = (value * ((10710 - (saturation * (42 - f))) / 42)) / 256;
18

19
        switch (i)
20
        {
21
            case 0: red = value;   green = t;     blue = p;   break;
22
            case 1: red = q;     green = value;   blue = p;   break;
23
            case 2: red = p;     green = value;   blue = t;   break;
24
            case 3: red = p;     green = q;     blue = value; break;      
25
            case 4: red = t;     green = p;     blue = value; break;        
26
            case 5: red = value;  green = p;     blue = q;    break;
27
      }
28
      setColorRGB(red, green, blue);
29
   }
30
}

P.S.: Die Größe der Funktion habe ich ermittelt indem ich einmal mit 
obigem Code kompiliert habe und einmal nachdem ich den gesamten Inhalt 
des Funktionsrumpfes auskommentiert hatte.
Gast #1498677
Lesenswert?

@Gast:

In der Tat müssen sie keine 16-Bit-Variablen sein. Daher habe ich
1
unsigned int  p, q, t;
ersetzt durch
1
unsigned char  p, q, t;


Der benötigte Flash-Speicherplatz reduziert sich jedoch nicht. Ich 
vermute, dass es daran liegt, dass während der Berechnung von p,q,t 
große Zahlen verwendet werden.
Gast #1498691
Lesenswert?

Hallo,

du verwendest ja alle 4 Grundrechenarten plus MOD und DIV, da ist das 
doch nicht viel, also wird sich auch kaum noch was wegoptimieren lassen.

Ich versuche bei µC oft, alle (!) Divisionen zu vermeiden, weil das die 
aufwendigste Operation ist. Also statt / 43 verwende ich * 0,023; das 
ist natürlich nur bei Konstanten sinnvoll. Es kann aber auch nach hinten 
losgehen, je nach Library und Linker, also ausprobieren.

Gruss Reinhard
Gast #1498841
Lesenswert?

>Der benötigte Flash-Speicherplatz reduziert sich jedoch nicht. Ich
>vermute, dass es daran liegt, dass während der Berechnung von p,q,t
>große Zahlen verwendet werden.

Bei den meisten der Multiplikationen drohen Überläufe.
8 Bit x 8Bit kann größer als 8 Bit werden. Da fehlen
dann noch geeignete casts. Dadurch wird der
Code aber noch größer.
Gast #1498964
Lesenswert?

So, ich hab mal ein bißchen rumgespielt:

Casting für die Multiplikationen eingesetzt und ein bißchen
ausgeklammert. Ist dabei von 514 auf 436 Bytes runter gegangen.
Komischerweise schrumpft der Code durch das Casting sogar;)
1
void setColorHSV(unsigned char hue, unsigned char saturation, unsigned char value)
2
{
3
    unsigned char i, f;
4
    unsigned char  p, q, t;
5
    unsigned char red = 0, green = 0, blue = 0;
6

7
    if (saturation == 0)  // Farbsättigung ist Null -> Grau
8
    {
9
        red = green = blue = 0;
10
    }
11
    else                  // Farbsättigung ist nicht Null -> Einteilen in 60°-Winkel des Farbtons
12
    {
13
        i = hue / 43;
14
        f = hue % 43;
15
        p = ((unsigned int)value * (255 - saturation)) / 256;
16
        q = (value * (10710 - ( (unsigned int)saturation * f) ) ) / 256 / 42;
17
        t = (value * (10710 - ((unsigned int)saturation * (42 - f)) ) ) / 256 / 42;
18

19
        switch (i)
20
        {
21
            case 0: red = value;   green = t;     blue = p;   break;
22
            case 1: red = q;     green = value;   blue = p;   break;
23
            case 2: red = p;     green = value;   blue = t;   break;
24
            case 3: red = p;     green = q;     blue = value; break;      
25
            case 4: red = t;     green = p;     blue = value; break;        
26
            case 5: red = value;  green = p;     blue = q;    break;
27
      }
28

29
      setColorRGB(red, green, blue);
30
   }
31
}
Gast #1498976
Lesenswert?

Ach shit:

        q = (value * (10710 - ( (unsigned int)saturation * f) ) ) / 256 
/ 42;

value * (10710 - ( (unsigned int)saturation * f)

Könnte wieder größer unsigned int werden.
Dann das ausklammern der 42 rückgängig machen.
Gast #1499034
Lesenswert?

Neuer Versuch;)
1
void setColorHSV(unsigned char hue, unsigned char saturation, unsigned char value)
2
{
3
    unsigned char i, f;
4
    unsigned char  p, q, t;
5
    unsigned char red = 0, green = 0, blue = 0;
6

7
    if (saturation == 0)  // Farbsättigung ist Null -> Grau
8
    {
9
        red = green = blue = 0;
10
    }
11
    else                  // Farbsättigung ist nicht Null -> Einteilen in 60°-Winkel des Farbtons
12
    {
13
        i = hue / 43;
14
        f = hue % 43;
15
        p = ((unsigned int)value * (unsigned char)(255 - saturation)) / 256;
16
        q = (value * ((10710 - ( (unsigned int)saturation * f) )  / 42) ) / 256;
17
        t = (value * ((10710 - ((unsigned int)saturation * (unsigned char)(42 - f)) )  / 42) ) / 256;
18

19
        switch (i)
20
        {
21
            case 0: red = value;   green = t;     blue = p;   break;
22
            case 1: red = q;     green = value;   blue = p;   break;
23
            case 2: red = p;     green = value;   blue = t;   break;
24
            case 3: red = p;     green = q;     blue = value; break;      
25
            case 4: red = t;     green = p;     blue = value; break;        
26
            case 5: red = value;  green = p;     blue = q;    break;
27
      }
28

29
      setColorRGB(red, green, blue);
30
   }
31
}

Ergibt nur noch 434 Bytes. Besser als 514 allemal.
Gast #1499043
Lesenswert?

Hallo Holger,

hier hast du eine Vorbelegung
1
    unsigned char red = 0, green = 0, blue = 0;

und hier wird im If diese bestädigt
1
    if (saturation == 0)  // Farbsättigung ist Null -> Grau
2
    {
3
        red = green = blue = 0;
4
    }
5
    else                  // Farbsättigung ist nicht Null -> Einteilen in 60°-Winkel des Farbtons


Besser:
1
    unsigned char red = 0, green = 0, blue = 0;
2

3
    if (saturation != 0)  // Farbsättigung ist nicht Null -> Einteilen in 60°-Winkel des Farbtons
4
    {

avr
Gast #1499052
Lesenswert?

>Hallo Holger,
>
>hier hast du eine Vorbelegung

Die ist nicht von mir;)

>    if (saturation != 0)  // Farbsättigung ist nicht Null -> Einteilen in 
>60°-Winkel des Farbtons

Ok, ausprobiert, bleibt bei 434 Bytes.
Gast #1499074
Lesenswert?

Schmeisst man das komplett raus

    if (saturation == 0)  // Farbsättigung ist Null -> Grau

Gibts noch mal 6 Bytes geschenkt.
Macht man aus dem switch() einen if(i==x) Spaghetti Code
nochmal 4 Bytes.

Ich glaub das wars dann aber auch. 424 Bytes.
Gast #1499102
Lesenswert?

>Ich würde es trozdem mal mit 255 anstelle von 256 versuchen... Das
>sollte dir auf jedenfall ein 16bit Division erstparen und das Ergebnis
>nicht merklich beeinflussen.

Bloß das nicht! Mit 256 zieht er sich nur das Highbyte des
Ergebnisses rein. Passt schon. Bei 255 müsste wirklich
dividiert werden.

>Kompilierst du auch mit Optimierung auf Platz?

Ja, Os.
Gast #1499443
Lesenswert?

Also erst einmal vielen Dank! Das ist in jedem Fall eine verbesserung, 
wenn auch eine kleine. Tatsächlich hat sich in der If-Bedingung ein 
Programmierfehler eingeschlichen, den aber keiner bemerkt hat:
1
void setColorHSV(unsigned char hue, unsigned char saturation, unsigned char value)
2
{
3
    unsigned char i, f;
4
    unsigned char  p, q, t;
5
    unsigned char red = 0, green = 0, blue = 0;
6

7
    if (saturation == 0)  // Farbsättigung ist Null -> Grau
8
    {
9
        red = green = blue = value;
10
    }
11
    else                  // Farbsättigung ist nicht Null -> Einteilen in 60°-Winkel des Farbtons
12
    {
13
        i = hue / 43;
14
        f = hue % 43;
15
        p = ((unsigned int)value * (unsigned char)(255 - saturation)) / 256;
16
        q = (value * ((10710 - ( (unsigned int)saturation * f) )  / 42) ) / 256;
17
        t = (value * ((10710 - ((unsigned int)saturation * (unsigned char)(42 - f)) )  / 42) ) / 256;
18

19
        switch (i)
20
        {
21
            case 0: red = value;   green = t;       blue = p;     break;
22
            case 1: red = q;       green = value;   blue = p;     break;
23
            case 2: red = p;       green = value;   blue = t;     break;
24
            case 3: red = p;       green = q;       blue = value; break;      
25
            case 4: red = t;       green = p;       blue = value; break;        
26
            case 5: red = value;   green = p;       blue = q;     break;
27
      }
28

29
      setColorRGB(red, green, blue);
30
   }
31
}
Das ist der Code mit Fehlerkorrektur und der Behebung des 
Programmierfehlers.


Ich bedanke mich für eure Unterstützung, möchte aber gleichzeitig auf 
keinen Fall dem Ehrgeiz einiger hier (insbesondere Holgers) einen 
Abbruch tun. Wenns noch besser geht, dann gerne! :-)
Gast #1499481
Lesenswert?

Etwas geht noch:
1
void setColorHSV(unsigned char hue, unsigned char saturation, unsigned char value)
2
{
3
    unsigned char i, f;
4
    unsigned char  p, q, t;
5
    unsigned char red, green, blue;
6

7
        red = green = blue = value;  //**  neue Vorbesetzung
8

9
//    if (saturation != 0)  //Farbsättigung ist nicht Null -> Einteilen in 60°-Winkel des Farbtons
10
//    {
11
        i = hue / 43;
12
        f = hue % 43;
13
        p = ((unsigned int)value * (unsigned char)(255 - saturation)) / 256;
14
        q = (value * ((10710 - ( (unsigned int)saturation * f) )  / 42) ) / 256;
15
        t = (value * ((10710 - ((unsigned int)saturation * (unsigned char)(42 - f)) )  / 42) ) / 256;
16

17
        switch (i)
18
        {        
19
                         // vorbesetzte Farben nutzen
20
            case 0:              green = t;       blue = p;  break;
21
            case 1: red = q;                      blue = p;  break;
22
            case 2: red = p;                      blue = t;  break;
23
            case 3: red = p;     green = q;                  break;      
24
            case 4: red = t;     green = p;                  break;        
25
            case 5:              green = p;       blue = q;  break;
26
      }
27

28
      setColorRGB(red, green, blue);
29
  // }
30
}


man kann durch die Vorbesetzung mit Value ganz auf
die If verzichten da das Case selektiert (hier nur mit //).

avr
Gast #1499606
Lesenswert?

> eine division durch 256 ist gleichbedeutend mit x>>8  8 bits nach rechts
> schieben, falls dein compiler das nicht schon macht ... probiers doch
> mal :)

Das is so ziemlich die älteste Optimierung, die ein Compiler von selber 
macht...
Gast #1499628
Lesenswert?

hallo,

die beiden Zeilen
((10710 - ( (unsigned int)saturation * f) )  / 42) )
((10710 - ((unsigned int)saturation * (unsigned char)(42 - f)) )  / 42)
ergänzen sich zu einer Konstanten:
-> die 2. Zeile ist durch simple Subtraktion der 1. Zeile von einer 
Konstanten (255/256) möglich.
#1499632
Lesenswert?

matze schrieb:
> @Frank:
>
> Hat das irgendwelche Nebenwirkungen? Warum macht das der Compiler nicht
> selbst? Und: Bei mir spart das genau 8 Byte.

das spart ein wenig am epilog der main-funktion, der ja nicht gebraucht 
wird, da ja normalerweise eine endlosschleife in der main werkeln.
aber wieviel es spart hängt glaub ich auch aein wenig vom controller ab, 
auf nem tiny13 hat mir das glaub ich 12 Byte gespart. auf nem mega88 
warns nur 8B
Gast #1499995
Lesenswert?

Hallo matze,

ich habe mal die Numerik etwas optimiert, den Switch von avr und den
noreturn von Frank übernommen:
1
#include <stdio.h>
2
#include <stdint.h>
3

4
volatile uint8_t dummy;
5

6
void setColorRGB(uint8_t r, uint8_t g, uint8_t b) {
7
  dummy = r; dummy = g; dummy = b;
8
}
9

10
void setColorHSV4(uint8_t h, uint8_t s, uint8_t v) {
11
  uint16_t vs=v*s, h6=6*h, f;
12
  uint8_t i, p, u, r=v, g=v, b=v;
13

14
  p = (v<<8)-vs >> 8;
15
  i = h6 >> 8;
16
  f = ((i|1)<<8) - h6;
17
  if(i&1)
18
    f = -f;
19
  u = ((uint32_t)v<<16) - (uint32_t)vs*f >> 16;
20
  switch(i) {
21
    case 0: g = u; b =  p; break;
22
    case 1: r = u; b =  p; break;
23
    case 2: r =  p; b = u; break;
24
    case 3: r =  p; g = u; break;
25
    case 4: r = u; g =  p; break;
26
    case 5: g =  p; b = u; break;
27
  }
28
  setColorRGB(r, g, b);
29
}
30

31
int main(void) __attribute__((noreturn));
32
int main(void) {
33
  int h=100, s=250, v=255;
34
  setColorHSV4(h, s, v);
35
  for(;;);
36
}

Man kommt jetzt ohne die Divisionen aus, und die Ergebnisse sind
genauer. Da aber statt der Division nun eine 32-Bit-Multiplikation
benötigt wird, ist der Code-Größe gegenüber der Version von avr kaum
geschrumpft, mit dem GCC 5.3.4 ist er sogar ein ganzes Stück größer
geworden.

Die folgenden Codegrößen beziehen sich jeweils auf das fertig gelinkte
Programm incl. der obigen Dummy-setColorRGB-Funktion, dem obigen Main
und den benötigten Bibliotheksfunktionen. Bei der Originalversion habe
ich den Datentyp der Argumente von char in unsigned char geändert, da
sonst nicht richtig gerechnet wird.
1
------------------------------------
2
AVR-GCC          4.2.4  4.3.4  4.4.2
3
------------------------------------
4
Original          506    476    458
5
Version von avr   438    412    398
6
meine Version     442    454    390
7
------------------------------------

Die Diagramme im Anhang zeigen die Abweichungen der Ergebnisse von
matzes ("deins") und meinem ("meins") Programm zum jeweils exakten Wert,
den ich mit Double-Genauigkeit berechnen lassen habe. Dabei ist s=250,
v=255, h läuft von 0 bis 255.

Ich habe mein Programm nur auf dem PC getestet, dabei aber durch
Einfügen zusätzlicher Casts die 16-Bit-Integer-Rechengenauigkeit des AVR
simuliert. Deswegen vermute ich, dass das Programm auch auf dem AVR
richtig rechnet, aber ganz sicher bin ich mir nicht ;-)

Falls Fehler auftreten, einfach meckern! Vielleicht kann ich dann noch
etwas geradebiegen.
Angehängte Dateien:

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren