Autore Topic: Bitmap->array->schermo a basso livello: endianess-safe!  (Letto 3294 volte)

Offline undead

  • Utente senior
  • ****
  • Post: 666
  • Respect: +113
    • Mostra profilo
  • Dispositivo Android:
    Samsung Galaxy S6
  • Play Store ID:
    DrKappa
  • Sistema operativo:
    Windows 10 64-bit, Windows 8.1 64-bit
Bitmap->array->schermo a basso livello: endianess-safe!
« il: 02 Aprile 2012, 16:16:07 CEST »
0
Prendo spunto da un topic in cui ho dato una risposta sbrigativa "al volo" per postare uno snippet che può servire se si vuole convertire una bitmap in un array e poi ridisegnarla su schermo dopo averla manipolata.

Il motivo per cui questo snippet può essere utile è che LE BITMAP E LA SURFACEVIEW A BASSO LIVELLO NON SONO COMPATIBILI!

Il primo problema è che la bitmap può essere scalata dal sistema a seconda della risoluzione del device. Il secondo problema è che il formato della bitmap non è conoscibile a prescindere. Il terzo problema è che anche la getPixel opera una conversione. Il quarto problema è che questa conversione è incompatibile con la surfaceview.

Per risolvere il primo problema bisogna richiedere che essa non venga scalata, così da evitare di generare colori che non esistono (col filtering) o saltare dei pezzi della bitmap. Per risolvere il secondo problema la bitmap deve essere forzata a 32-bit.

Codice (Java): [Seleziona]
// Opzioni bitmap
BitmapFactory.Options opt = new BitmapFactory.Options();
// NON scalare la bitmap
opt.inScaled = false;
// Riporta la bitmap in ARGB_8888
opt.inPreferredConfig = Bitmap.Config.ARGB_8888;

A questo punto è risolto anche il terzo problema perchè la getPixel riporta un Color, che è ARGB8888, cioè effettivamente quello che è stato importato.

Non solo è più veloce, ma è fedele al 100% perchè non effettua alcuna conversione.

Per quanto riguarda la surfaceview le cose si fanno più complicate, perchè dobbiamo essere sicuri che lo schermo sia in un formato compatibile. Fino a froyo (credo incluso) la surfaceview di default è 565, non 888!!!! Questo è il primo motivo di incompatibilità che impedisce di ridisegnare a basso livello.

Per ovviare (in teoria) a questo problema bisogna settare il formato dell'holder:

Codice (Java): [Seleziona]
m_SurfaceHolder = getHolder();
m_SurfaceHolder.setFormat(PixelFormat.RGBA_8888);

Come si capisce chiaramente il formato non è lo stesso:
ARGB_8888 per le bitmap/Color
RGBA_8888 per la surfaceview

E qui nasce il problema. Al contrario di quanto ci si può aspettare NON TUTTI I COLORI SONO FUORI POSTO!!!

In pratica le definizione di color dice che:

color = (alpha << 24) | (red << 16) | (green << 8) | blue

Ma se disegno sul mio device l'array secondo il formato corretto  i colori sono sballati!!!

Funziona solo con:

color = (alpha << 24) | (blue << 16) | (green << 8) | red

Quindi in realtà l'holder non sembrerebbe essere un RGBA8888 ma un ABGR8888. Di fatto se passo pari pari quello che mi riporta la getPixel ho il rosso e il blu invertiti.

Come è possibile?!?!?!?!?

E' possibile perchè a basso livello entra in gioco l'endianess della CPU!!!!

Nel mio caso la CPU è little endian quindi ABGR viene letto al contrario cioè RGBA che guardacaso è esattamente il formato che ci serve!!!!

Se la CPU è big endian (cosa che dubito) la conversione è differente:

color = (red << 24) | (green << 16) | (blue << 8) | alpha

Come fare per sapere l'endianess di un device? Semplice:

Codice (Java): [Seleziona]
boolean isLittleEndian = false;

if( ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN)
 isLittleEndian = true;

A questo punto potete tranquillamente convertire la vostra immagine in un array, manipolarla e ridisegnarla su schermo senza timore di avere colori sballati, indipendentemente dalla versione dell'OS e tenendo conto dell'endianess del device testando la variabile isLittleEndian.

 ;-)
« Ultima modifica: 02 Aprile 2012, 17:35:16 CEST da undead »

Offline bradipao

  • Moderatore globale
  • Utente storico
  • *****
  • Post: 4043
  • keep it simple
  • Respect: +567
    • Github
    • Google+
    • bradipao
    • Mostra profilo
  • Dispositivo Android:
    Nexus 5
  • Play Store ID:
    Bradipao
  • Sistema operativo:
    W7
Re:Bitmap->array->schermo a basso livello: endianess-safe!
« Risposta #1 il: 02 Aprile 2012, 17:07:05 CEST »
0
Comprendo (e ci credo) che ci sono problemi di posizioni dei byte, ma ci sono una paio di cose che non mi tornano.

1) Ai giorni nostri (grazie al cielo) sopravvivono solo 2 famiglie di cpu big-endian : PowerPC e SPARC. Sia ARM che x86 sono little-endian.

2) Poi,

color = (alpha << 24) | (red << 16) | (green << 8) | blue  equivale a ARGB e non RGBA

color = (alpha << 24) | (blue << 16) | (green << 8) | red  equivale giustamente a ABGR
NON rispondo a domande nei messaggi privati
Bradipao @ Play Store

Offline undead

  • Utente senior
  • ****
  • Post: 666
  • Respect: +113
    • Mostra profilo
  • Dispositivo Android:
    Samsung Galaxy S6
  • Play Store ID:
    DrKappa
  • Sistema operativo:
    Windows 10 64-bit, Windows 8.1 64-bit
Re:Bitmap->array->schermo a basso livello: endianess-safe!
« Risposta #2 il: 02 Aprile 2012, 17:33:51 CEST »
0
Comprendo (e ci credo) che ci sono problemi di posizioni dei byte, ma ci sono una paio di cose che non mi tornano.

1) Ai giorni nostri (grazie al cielo) sopravvivono solo 2 famiglie di cpu big-endian : PowerPC e SPARC. Sia ARM che x86 sono little-endian.

2) Poi,

color = (alpha << 24) | (red << 16) | (green << 8) | blue  equivale a ARGB e non RGBA

color = (alpha << 24) | (blue << 16) | (green << 8) | red  equivale giustamente a ABGR
1) Hai ragione, mi sono sbagliato.  :-) Android però prevede anche un byte order big endian e l'architettura arm è bi-endian (switchabile ma di default little endian) quindi a scanso di equivoci secondo me se si fanno operazioni a basso livello è comunque buona norma controllare il byte order nativo. Correggo subito, grazie!
2)Vediamo se mi spiego meglio.

La getPixel riporta un Color, che è in formato ARGB, la definizione che hai postato è corretta.
Purtroppo però la surfaceview è in formato RGBA.

Se io leggo il color (in ARGB), lo manipolo componente per componente e provo a ridisegnarlo sulla surfaceview dovrei ottenere colori tutti sballati perchè per quei due formati:
ARGB
RGBA
nessuna componente è nella posizione giusta.

Quello che ottengo invece è che il rosso ed il blu sono invertiti!

Questo accade per l'endianess del device per cui se io ho 4 byte sequenziali in memoria:

0x11
0x22
0x33
0x44

In little endian sono interpretati come

0x44332211

Quindi se la mia surface vuole un colore RGBA e io lo scrivo in memoria byte per byte:

0xRR
0xGG
0xBB
0xAA

mi viene in realtà letto come:

0xAABBGGRR

cioè ABGR, ovvero il formato iniziale (ARGB) con i componenti B ed R invertiti!

 :-)

Offline bradipao

  • Moderatore globale
  • Utente storico
  • *****
  • Post: 4043
  • keep it simple
  • Respect: +567
    • Github
    • Google+
    • bradipao
    • Mostra profilo
  • Dispositivo Android:
    Nexus 5
  • Play Store ID:
    Bradipao
  • Sistema operativo:
    W7
Re:Bitmap->array->schermo a basso livello: endianess-safe!
« Risposta #3 il: 02 Aprile 2012, 18:21:11 CEST »
0
Grazie per la precisazione sul colore dei pixel.  :-) Effettivamente è impostata abbastanza male dal framework.

Riguardo l'endianess, per motivi strettamente professionali, vorrei sapere a chi è venuto in mente di inventare il big-endian, che è fondamentalmente sbagliato rispetto al little-endian (dimostrabile).  :-P
NON rispondo a domande nei messaggi privati
Bradipao @ Play Store

Offline iceweasel

  • Moderatore globale
  • Utente senior
  • *****
  • Post: 878
  • Respect: +147
    • Mostra profilo
  • Dispositivo Android:
    LGE P990 - Google Nexus 5
  • Sistema operativo:
    Linux Debian Sid
Re:Bitmap->array->schermo a basso livello: endianess-safe!
« Risposta #4 il: 02 Aprile 2012, 19:47:08 CEST »
0
ARM è sia little-endian che big-endian, di solito è il sistema operativo ad impostarlo al boot. ARM se configurato little-endian l'architettura si chiama sempre ARM o ARMEL, se configurato big-endian, di solito, si indica con ARMEB.

PowerPC e SPARK nascono big-endian ma poi diventano configurabili anche loro.

Le CPU Intel e AMD della famiglia x86 e X86_64 sono fisse little-endian mentre Intel Itanium IA-64 è configurabile.

Android non assicura se little-endian o big-endian, infatti API ha la classe ByteOrder per saperlo:

ByteOrder | Android Developers

Il big-endian è più simile a come noi esseri umani scriviamo i numeri (sistema numerico arabo), ma little-endian per me porta dei vantaggi quando si troncano o si fanno dei cast sui numeri in memoria.

Qui c'è un PDF che spiega i pro e in contro dei due sistemi:

http://www.intel.com/design/intarch/papers/endian.pdf

Il problema sul l'ordine delle componenti colore delle superficie grafica dipende dalla GPU utilizzata, molto spesso l'ordine non è compatibile o direttamente leggibile dalla CPU senza una conversione.
adb logcat | tee /tmp/logcat | grep TAG

Offline bradipao

  • Moderatore globale
  • Utente storico
  • *****
  • Post: 4043
  • keep it simple
  • Respect: +567
    • Github
    • Google+
    • bradipao
    • Mostra profilo
  • Dispositivo Android:
    Nexus 5
  • Play Store ID:
    Bradipao
  • Sistema operativo:
    W7
Re:Bitmap->array->schermo a basso livello: endianess-safe!
« Risposta #5 il: 02 Aprile 2012, 20:16:18 CEST »
0
In realtà è il little-endian ad essere esattamente come scriviamo i numeri noi esseri umani. Infatti a prescindere dalla dimensione in byte del numero, ti basta leggere i byte in ordine e avrai il numero pronto ed utilizzabile. Se invece sei in big-endian devi tenere conto che ogni N byte (con N pari al numero di byte per word) devi ribaltarli di posizione e se leggi non word-aligned i byte da leggere potrebbero anche non essere consecutivi.

Il big-endian ha solo svantaggi rispetto al little (si vede abbastanza quanto lo odio?  :-P ).

EDIT : è una avversione che proviene dall'enorme fastidio che mi procura dover fare debugging sul bus di una cpu sparc con l'analizzatore di stati logici.
« Ultima modifica: 02 Aprile 2012, 20:26:19 CEST da bradipao »
NON rispondo a domande nei messaggi privati
Bradipao @ Play Store

Offline undead

  • Utente senior
  • ****
  • Post: 666
  • Respect: +113
    • Mostra profilo
  • Dispositivo Android:
    Samsung Galaxy S6
  • Play Store ID:
    DrKappa
  • Sistema operativo:
    Windows 10 64-bit, Windows 8.1 64-bit
Re:Bitmap->array->schermo a basso livello: endianess-safe!
« Risposta #6 il: 03 Aprile 2012, 08:48:17 CEST »
0
Sempre interessanti queste discussioni "a basso livello".  :-)

Comunque va beh il concetto del  mio post/snippet era riassumibile in:

1- fate attenzione a non scalare la bitmap perchè se ve la allarga vi vengono valori intermedi
2- settate comunque ARGB8888 perchè quello è il formato che riporta la getPixel, tanto vale prevenire ogni problema
3- occhio che la surfaceview è RGBA8888 e la bitmap è ARGB8888 (scelta folle a mio avviso)
4- se eventualmente lavorate componente per componente (per esempio lavorando con puntatori/array in NDK) controllate l'endianess col ByteOrder e regolatevi di conseguenza

 :-)

Offline undead

  • Utente senior
  • ****
  • Post: 666
  • Respect: +113
    • Mostra profilo
  • Dispositivo Android:
    Samsung Galaxy S6
  • Play Store ID:
    DrKappa
  • Sistema operativo:
    Windows 10 64-bit, Windows 8.1 64-bit
Re:Bitmap->array->schermo a basso livello: endianess-safe!
« Risposta #7 il: 03 Aprile 2012, 08:55:13 CEST »
0
Il problema sul l'ordine delle componenti colore delle superficie grafica dipende dalla GPU utilizzata, molto spesso l'ordine non è compatibile o direttamente leggibile dalla CPU senza una conversione.
Sono pienamente d'accordo a metà con iceweasel.  :-P

Niente da eccepire sul ragionamento: la GPU può supportare qualunque formato.

In pratica però gli unici formati settabili dal programmatore sono RGB888, RGBX8888, RGBA8888.  Il fatto che internamente avvenga una conversione è fuori dal nostro controllo.

Se la tua sorgente è ARGB8888 la conversione a RGBA8888 devi farla lo stesso, anche se poi internamente per assurdo venisse riconvertita in ARGB8888.  :-(

ps a scanso di equivoci parlo sempre di caricare una bitmap, copiarla su un array e manipolarselo con NDK. Caricare una bitmap e disegnarla con funzioni normali è tutt'altra storia.

Offline LucaP84

  • Utente junior
  • **
  • Post: 50
  • Respect: +1
    • Mostra profilo
  • Sistema operativo:
    Seven
Re:Bitmap->array->schermo a basso livello: endianess-safe!
« Risposta #8 il: 29 Maggio 2012, 16:03:24 CEST »
0
Ciao usa questa matrice: io uso questa.
a presto Luca

           // Increase Contrast, Slightly Reduce Brightness
           float contrast = 3;
           float brightness = -5;
           cm.set(new float[] { contrast, 0, 0, 0, brightness, 0,
                           contrast, 0, 0, brightness, 0, 0,
                           contrast, 0, brightness, 0,
                           0, 0, 1, 0 });

         
         paint.setColorFilter(new ColorMatrixColorFilter(cm));