Entendendo o funcionamento das Cores, dos Pixels e o formato RGB24

Entendendo o funcionamento das Cores, dos Pixels e o formato RGB24

Olá leitor, seja muito bem vindo a mais uma jornada aqui no Portal da Micilini 😊

O conteúdo que você está prestes a ver agora, faz parte de um longo treinamento de mais ou menos 20 artigos no total, que cobrem todo o pensamento teórico sobre como as cores funcionam, até a construção de um CODEC 100% autoral que será feito por você!

Mas antes de continuar, preciso que você esteja OK com essas ideias:

  • Nós iremos programar o nosso próprio CODEC na linguagem C;
  • Talvez façamos algumas coisinhas em ASSEMBLY para ganhar um pouco de performance;

E já te adiantando o que iremos aprender por aqui, a ideia é que no final dessa jornada você seja capaz criar seu próprio CODEC de imagens.

Mas dai você pode estar se perguntando...

O que vem a ser um CODEC de imagens? 🤔

O que é um CODEC?

De forma bem resumida (porque vamos nos aprofundar nisso mais pra frente), CODEC é uma junção de duas palavras: COder (codificador) + DECoder (decodificador).

Sendo assim, podemos dizer que um CODEC é basicamente um "programa" (ou algoritmo) que sabe fazer duas coisas:

➡️ Pegar uma imagem crua (geralmente enorme, pesada, cheia de dados).

➡️ Comprimir ela em um arquivo menor.

E depois, quando alguém quiser ver essa imagem (computador, celular, TV etc), ele saiba descomprimir e reconstruir a imagem de volta (no formato cru).

Imagina que você tem uma folha de papel A4 em suas mãos, ela é grande, e apesar de ser leve, querendo ou não, ela pode ocupar um espaço bem grande na sua mesa de trabalho. Concorda comigo?

Agora imagine que você precise levar essa folha da sua casa direto para o seu trabalho. É ruim ficar carregando um pedaço de papel por aí por mais leve que ele seja, né? 🤭

Se você for uma pessoa esperta, talvez a sua primeira reação seja pegar essa folha de papel A4, dobrar em mais ou menos uns 7 pedaços e colocar no bolso.

Isso economizaria bastante espaço, pois você poderia carregar mais coisas na mão, e quando chegar ao seu trabalho, é só desdobrar 😎

Observação: Considere que vivemos em um mundo onde desdobrar um folha de papel não deixe marcas da dobra, ok?

Um CODEC faz justamente isso, o arquivo que será manipulado (vídeo, imagem, áudio etc) é a sua folha de papel A4, o fato de você dobrar ela em 7 partes diferentes é o que chamados de codificação, e o ato de desdobrar (retornando ao seu tamanho original) é chamado de decodificação.

Sabe aqueles colchões a vácuo que você compra de forma comprimida? Então, apesar deles serem "pesados", concorda comigo que eles ocupam menos espaço, caso fossem transportados em sua forma natural?

Se você entende isso, você já sabe o que é um CODEC 😉

As estenções de CODEC mais conhecidas

Sabe aquele arquivo .jpg que você tira com a câmera do seu celular?

Pois saiba que ele passou por um CODEC chamado JPEG, que comprimiu a sua imagem pra caber em poucos kilobytes em vez de ocupar megabytes.

E aquele .png que você usa quando quer uma imagem sem perda de qualidade? Ele também passou por um CODEC, o CODEC do PNG😆

E não, diferente do que você pensa, a foto original, da forma mais BRUTA possível, ela não é em formato .jpg, .png, .bmp nem qualquer outro formato que você conheça, o formato bruto tem um nome e é chamado de RGB24, mas calma que já vamos falar sobre ele...

Voltando ao assunto principal, PNG, JPEG, WebP, GIF, BMP... todos esses formatos de imagem que você já conhece, por trás de cada um deles existe um CODEC responsável por comprimir e descomprimir os dados.

A diferença entre eles está na forma como cada um faz essa compressão. Alguns perdem um pouquinho de qualidade pra deixar o arquivo menor (como o JPEG), outros mantêm a qualidade intacta mas não conseguem comprimir tanto (como o PNG).

Ou seja, até aqui já deu pra ver que cada um deles tem sua própria estratégia de compressão.

Observação: no final da nossa jornada, a ideia é entender cada uma dessas estratégias de compressão de modo a criar o nosso próprio CODEC de imagens do zero, e 100% autoral.

Mas calma, não precisa surtar 😅

Te prometo que vou deixar tudo bem mastigadinho, te ajudando a vai chegar lá um passo de cada vez.

RGB24: A matéria prima, uma imagem em sua forma mais crua

O primeiro passo, é entender a matéria-prima que todo CODEC de imagem recebe como entrada: uma imagem crua, sem compressão nenhuma, no formato que chamamos de RGB24.

RGB24 é como se fosse a nossa folha de papel A4 sem dobras. Pense no RGB24 como aquele bife cru que chega na cozinha antes do chef preparar o prato.

Ele é grande, pesado, sem tempero nenhum. O trabalho do CODEC é pegar esse "bife cru" e transformar num prato bonito, compacto e saboroso.

Mas antes de aprender a cozinhar, a gente precisa entender o que tem dentro desse bife 👀

Porém, para entendermos isso, o ideal é entender o que são cores e como nossos olhos enxergam o mundo ao nosso redor.

Então vamos começar bem do começo mesmo... e sua jornada começa agora 🚀

Antes de tudo, o que é Cor?

Antes de sairmos criando nosso próprio CODEC de imagens, precisamos dar alguns passos para trás e entender de verdade como uma imagem funciona.

E quando eu digo "de verdade", eu quero dizer que vamos começar do começo mesmo:

  • Entendendo o que é cor?
  • Como o olho humano enxerga uma cor?
  • O que vem a ser um Pixel?
  • E por fim, como bytes são transformados dentro de um computador?

Então senta aí, pega um café ☕, e vamos nessa!

Pra começar, eu preciso te fazer uma pergunta simples: O que é cor?

Se você respondeu algo como "cor é o que a gente vê nos objetos", você está no caminho certo, mas falta um pedaço importante nessa história toda.

A verdade é que cor não existe nos objetos

Isso mesmo, você leu certo!

Sabe aquele maça vermelha que está na sua cozinha? Ela não é vermelha!

O céu também não é azul, a grama não é verde, e esse cachorro da foto acima nem é marrom de verdade 🤣

Ou pelo menos, não da forma como a gente imagina!

Uma cor nada mais é do que a sensação que o nosso cérebro cria a partir da luz que chega aos nossos olhos.

Ela não passa de uma interpretação, uma ilusão caso você queira chamar dessa maneira.

Mas peraí William, se nada tem cor... como isso funciona em nível atômico?

Boa pergunta! E a resposta é tão maluca que vale a pena ir um pouquinho mais fundo.

Tudo no universo é feito de átomos. Seu celular, sua mesa, sua pele, aquela maçã vermelha da cozinha, e aquele cachorro da foto acima.

E um átomo é composto basicamente por um núcleo (prótons e nêutrons) com elétrons orbitando ao redor dele.

E aqui vem o ponto: um átomo, sozinho, não tem cor nenhuma.

Segundo o Dr. Christopher S. Baird, professor de Física da West Texas A&M University, a cor de um objeto é resultado da interação da luz com milhões de átomos ao mesmo tempo (um fenômeno chamado de reflexão, refração e absorção em massa), e não de uma cor individual de cada átomo.

Isso faz todo o sentido quando você descobre um detalhe incrível: a luz visível tem um comprimento de onda entre 400 e 700 nanômetros, enquanto um átomo tem cerca de 0,2 nanômetros de largura. Ou seja, um fóton de luz é aproximadamente 2000 vezes maior que um átomo!

É como tentar jogar uma bola de futebol em uma formiga, ou seja, a bola nem "enxerga" a formiga individualmente.

Sendo assim, quando a luz branca do SOL (que contém todas as cores misturadas) bate na superfície de um objeto, os elétrons dos átomos que compõem aquele objeto absorvem parte dessa luz e refletem outra parte. A cor que o objeto "parece ter" é simplesmente a cor da luz que foi refletida de volta pros seus olhos.

Experimente entrar dentro de uma sala com luz vermelha, azul, verde pra ver se você não absorve parte dessa cor 😂

No caso da maçã: os elétrons dos átomos da casca dela absorvem as ondas de luz verde, azul, amarela, laranja... e refletem de volta as ondas vermelhas. Essas ondas vermelhas chegam até o seu olho, atingem os cones da retina, e aí o seu cérebro diz: "isso é vermelho!".

A parte mais interessante é que: o vermelho não estava "na" maçã, na verdade, ele estava na luz. A maçã só refletiu aquela parte.

Outro pesquisador chamado de Brent Nelson, pesquisador de Física da UC Berkeley, resume isso de forma ainda mais clara:

"No nível subatômico, cor não é uma propriedade intrínseca de nada. Um mesmo elétron é capaz de emitir raios-X, luz laranja ou ondas de rádio — tudo depende do ambiente e das interações em que ele se encontra".

Ou seja: a cor não mora nos átomos. A cor mora no encontro entre a luz, a matéria e o seu cérebro. Sem algum desses três, cor simplesmente não existe.

E se não tiver luz? Aí não temos cor, e é por isso tudo fica "preto" no escuro.

Mas e se não existir o olho e o cérebro para interpretar? A luz ainda continua lá (existindo), vibrando em suas frequências, mas ninguém vai "ver" cor nenhuma.

É por isso que dizemos que cor é uma percepção, não uma propriedade física do objeto.

Sendo assim, podemos dizer que a cor é criada pelo trio luz + matéria + cérebro.

Tudo começa com a Luz

A luz que ilumina o nosso dia a dia vem, na maioria das vezes, do Sol. E o que a gente chama de "luz branca" do Sol na verdade é uma mistura de várias cores juntas.

Sabe quando você vê um arco-íris no céu depois de uma chuva? Aquelas faixas coloridas que vão do vermelho ao violeta? Então, aquilo é a luz do Sol sendo decomposta (separada) em suas diferentes cores.

A luz é uma onda eletromagnética. Cada cor que a gente enxerga corresponde a um comprimento de onda diferente. O vermelho tem um comprimento de onda mais longo, e o violeta tem um comprimento mais curto.

E as outras cores que existem? Simples, elas ficam no meio, entre o violeta e o vermelho.

O espectro visível (a faixa de luz que nossos olhos conseguem ver) vai de aproximadamente 380 nanômetros (violeta) até 700 nanômetros (vermelho).

Tudo o que está fora dessa faixa (como ultravioleta ou infravermelho) nossos olhos simplesmente não conseguem captar.

Pense assim: existe um "mundo de cores" gigantesco lá fora, mas nossos olhos só enxergam uma faixa bem pequena dele.

É por esse motivo que é tão difícil imaginar uma cor nova

💡 Curiosidade: Alguns animais, como as abelhas, enxergam ultravioleta. Outros, como as cobras, enxergam infravermelho. Cada espécie vê o mundo de um jeito!

Mas dai você pode estar se perguntando, se só as cobras veem aquele vermelho vibrante da imagem acima, como é que eu tô vendo ele na foto? O meu teclado RGB tem a mesma cor e é visível pra mim 🫣

Boa pergunta! E a resposta é mais simples do que parece.

O que está acontecendo naquela imagem (e no seu teclado RGB) não é infravermelho de verdade.

É apenas vermelho comum, ou seja, uma cor que está dentro do espectro visível, entre 620nm e 700nm, que seus olhos enxergam perfeitamente.

O infravermelho que as cobras enxergam começa depois dos 700nm, numa faixa que nossos olhos simplesmente não captam. 

Se fosse infravermelho de verdade ali representado naquela imagem, você simplesmente não conseguiria enchergar, seria invisível para você, assim como as ondas eletromagnéticas do seu WI-FI são invisíveis para você.

Então quando a gente mostra uma ilustração do "espectro completo" numa tela, o que o monitor está fazendo é uma aproximação visual. Ele usa as cores que ele consegue produzir (vermelho, verde e azul) pra te dar uma ideia de como seria aquela faixa.

É como se alguém tentasse te explicar o gosto de uma fruta que você nunca comeu dizendo "é tipo manga com um toque de morango". Não é a fruta de verdade, é uma aproximação usando sabores que você já conhece.

Da mesma forma, aquele vermelho escuro lá no cantinho direito do espectro na ilustração não é infravermelho real.

É o vermelho mais escuro que o monitor consegue mostrar, como quem diz: "daqui pra frente, seus olhos não acompanham mais" 😄

🧠 Resumindo: Tudo que você vê numa tela está obrigatoriamente dentro do espectro visível, porque a tela só produz luz RGB. Ela é fisicamente incapaz de emitir ultravioleta ou infravermelho. Então qualquer representação dessas faixas numa tela é sempre uma aproximação, nunca a coisa real.

Essa seria a sua visão caso você fosse capaz de enxergar acima de 700nm:

Que loucura seria, não?

Mas dai, você pode estar se perguntando... e aqueles lasers infravermelhos que a gente vê nos filmes de espião?

Sabe aquelas cenas clássicas em que o espião joga um pozinho no ar e de repente aparecem vários feixes de luz vermelha cortando a sala? E ele precisa desviar de todos eles pra não disparar o alarme?

Se o infravermelho é invisível pro olho humano, como é que o espião (e a gente assistindo o filme) consegue ver aqueles feixes?

A resposta é: não consegue. Pelo menos não na vida real.

No cinema, eles mostram os feixes como linhas vermelhas brilhantes porque, convenhamos, seria um filme bem chato se o espião entrasse na sala e não visse absolutamente nada 😅

Eles usam vermelho justamente porque é a cor visível mais próxima do infravermelho no espectro, então o cérebro do espectador associa aquilo com "algo perigoso e high-tech".

Na vida real, sistemas de segurança com sensores infravermelhos são completamente invisíveis a olho nu. Você não vê feixe nenhum. Não tem como jogar pozinho e revelar a luz.

O feixe está lá, atravessando a sala, mas numa frequência que seus cones simplesmente ignoram.

Agora, e o laser de brinquedo? Aquele pontinhos vermelho que você usava pra brincar com o gato?

Aquilo é vermelho de verdade, até porque a luz visível, geralmente na faixa de 630nm a 670nm está 100% dentro do espectro visível. Não tem nada de infravermelho ali, ok?

Existem, sim, lasers infravermelhos reais (usados em controles remotos de TV, por exemplo). Se você apertar o botão do controle remoto e olhar diretamente pro LED dele, você não vai ver nada a olho nu.

Mas se você apontar a câmera do celular pro controle e apertar o botão, vai ver um pisca-pisca roxo/branco na tela.

Isso acontece porque o sensor da câmera do celular consegue captar uma faixa um pouquinho maior que o olho humano, entrando levemente no infravermelho próximo.

Então sim, Hollywood mentiu pra você 😉

Como o Olho Humano enxerga as Cores?

Agora que você já entende que uma cor nada mais é do que a própria LUZ em ação. Vamos entender de forma mais biológica como o nosso olho capta essa informação de cor.

O olho humano é basicamente uma câmera biológica, e isso significa que a luz entra pela pupila (aquele círculo preto no centro do olho), passa por uma lente (o cristalino) e é projetada no fundo do olho, numa membrana chamada retina.

A retina é onde a mágica acontece, e por sua vez, é coberta por milhões de células fotossensíveis (sensíveis a luz) que se dividem em dois tipos:

Bastonetes — São cerca de 125 milhões em cada olho.

Eles são extremamente sensíveis à luz, mas não distinguem cores. São responsáveis pela nossa visão noturna e pela visão periférica (aquilo que a gente vê "pelo canto do olho").

É por isso que à noite tudo parece cinza e sem cor, até porque nessa condição de pouca luz, são os bastonetes que estão trabalhando.

Cones — São cerca de 7 milhões em cada olho.

Eles precisam de bastante luz para funcionar, mas são os responsáveis pela visão colorida e pela nitidez das imagens.

Os 3 Tipos de Cones: A Base de Tudo

Os cones se dividem em 3 tipos, e cada tipo é sensível a uma faixa diferente de comprimento de onda:

Cones S (Short / Curtos): sensíveis a ondas curtas, perto do azul (~440nm).

Cones M (Medium / Médios): sensíveis a ondas médias, perto do verde (~540nm).

Cones L (Long / Longos): sensíveis a ondas longas, perto do vermelho (~580nm).

Ou seja, o nosso olho enxerga basicamente Vermelho, Verde e Azul, e todas as outras cores que a gente vê, como amarelo, laranja, rosa, roxo, marrom, ciano, magenta, são combinações dessas três cores sendo interpretadas pelo nosso próprio cérebro.

Quando a luz amarela do semáforo atinge seu olho, ela estimula igualmente os cones vermelhos e os cones verdes. O cérebro recebe essa combinação e interpreta como "amarelo".

Quando o vermelho e o azul são estimulados ao mesmo tempo, o cérebro interpreta como "magenta", e por aí vai...

Quando os três tipos de cones são estimulados de forma igual e intensa, o cérebro interpreta como branco.

Quando nenhum cone é estimulado, o cérebro interpreta como preto que nada mais é do que a própria ausência da luz.

🧠 Sacou a lógica? Nosso olho funciona como um sensor RGB natural! Vermelho (R), Verde (G) e Azul (B). E é exatamente por isso que os monitores e telas usam o sistema RGB. Eles foram projetados para enganar nossos olhos da mesma forma que a natureza faz.

Mas como exatamente esses cones funcionam? Como assim um só "aceita" vermelho e outro só "aceita" verde?

Pra entender isso, imagina o seguinte: você tem 3 copos na sua frente. Cada copo tem um formato de boca diferente.

O primeiro copo tem a boca estreita e pequena, só cabe uma bolinha azul ali dentro.

O segundo copo tem a boca média, e só cabe uma bolinha verde.

O terceiro copo tem a boca larga, mas só cabe uma bolinha vermelha.

Agora imagina que a luz é como alguém jogando bolinhas de várias cores na direção desses 3 copos o tempo todo. As bolinhas que "encaixam" na boca do copo caem lá dentro. As que não encaixam batem na borda e vão embora.

Seus cones funcionam exatamente assim. Cada tipo de cone tem uma "abertura" molecular (uma proteína chamada opsina) que só responde a um determinado comprimento de onda.

Se a onda "encaixa", o cone dispara um sinal elétrico pro cérebro. Se não encaixa, ele fica quieto, simplesmente não age, não tem nenhuma ação rs

E se alguém jogar uma bolinha amarela? Ela não encaixa perfeitamente em nenhum dos 3 copos! Então o que acontece?

A bolinha amarela tem um comprimento de onda (~570nm) que fica entre o verde e o vermelho. Ela não encaixa perfeito no copo verde, mas quase encaixa. Também não encaixa perfeito no copo vermelho, mas quase encaixa. Então os dois copos recebem um pouquinho cada um.

O cérebro recebe o sinal: "copo verde ativou parcialmente, copo vermelho ativou parcialmente, copo azul ficou quieto".

E é dentro dessa combinação, que ele inventa a sensação daquilo que a gente chama de amarelo.

O amarelo não existe como sinal único no seu olho. Nenhum cone é especialista em amarelo. O amarelo é uma dedução que seu cérebro faz a partir da ativação parcial de dois cones diferentes.

🎯 Pra fixar: Seus olhos não têm milhões de sensores diferentes pra milhões de cores. Você tem apenas 3 tipos de sensor (os cones S, M e L). Toda a riqueza de cores que você enxerga é o seu próprio cérebro sendo um gênio da combinação, montando milhões de receitas a partir de só 3 ingredientes.

Cor-Luz vs Cor-Pigmento

Antes de seguir em frente, preciso esclarecer uma confusão super comum que a maioria de nós temos.

Quando você estava na escola, provavelmente aprendeu que as cores primárias são Vermelho, Amarelo e Azul, certo? E que misturando elas você consegue todas as outras cores.

Sinto lhe informar, mas essa informação que você aprendeu está parcialmente certa 🤨

Ela se refere às cores-pigmento (subtrativas), que é o que acontece quando você mistura tintas, guaches, lápis de cor. Nesse caso, a mistura funciona subtraindo luz (cada pigmento absorve certas cores e reflete outras).

No mundo digital, a gente trabalha com cor-luz (aditiva). Aqui as cores primárias são Vermelho (Red), Verde (Green) e Azul (Blue) o famoso RGB.

A mistura funciona adicionando luz, e quanto mais você adiciona, mais claro fica.

Observe a diferença:

Cor-Pigmento (Subtrativa - CMYK): As primárias são Ciano, Magenta e Amarelo. Misturando todas, você tende ao preto (ausência de luz refletida). É usada em impressoras.

Cor-Luz (Aditiva - RGB): As primárias são Vermelho, Verde e Azul. Misturando todas, você tende ao branco (soma de todas as luzes). É usada em telas e monitores.

A partir de agora, quando falarmos de cores nesta jornada, estaremos sempre falando de cor-luz (RGB), porque é assim que os computadores e monitores representam cores, tudo bem?

A Proporção dos Cones e o porquê do Verde ser especial

Agora vou trazer mais uma grande curiosidade pra você 😁

Você sabia que os 3 tipos de cones não existem em proporções iguais na retina?

  • Os cones verdes (M) representam cerca de 72% do total de cones.
  • Os cones vermelhos (L) representam cerca de 21%.
  • Os cones azuis (S) representam apenas 7%.

Isso significa que nossos olhos são muito mais sensíveis ao verde do que ao vermelho ou ao azul.

Mas por quê? A explicação está na evolução 🧬

Nossos ancestrais viviam em ambientes com muita vegetação, e a capacidade de detectar diferenças sutis de verde era crucial para sobrevivência, como: identificar um predador camuflado entre as folhas, diferenciar uma fruta boa de uma estragada, ou perceber movimentos no meio da mata.

Mas espera ai.... Se nosso olho é tão bom em detectar verde, como é que um soldado vestindo camuflado verde consegue se esconder no meio da mata?

A resposta é que a camuflagem militar não tenta enganar os seus cones. Ela tenta enganar o seu cérebro.

Seus cones estão lá, captando verde perfeitamente.

O problema é que quando um soldado usa um uniforme que mistura vários tons de verde, marrom e preto em manchas irregulares, o cérebro não consegue identificar a forma de um corpo humano no meio de tanta informação verde semelhante.

Funciona assim: seu cérebro é treinado pra reconhecer padrões e contornos.

Quando você olha pra uma floresta, seu cérebro procura por silhuetas familiares, o formato de um rosto, de ombros, de um corpo em pé. A camuflagem "quebra" esses contornos misturando o formato do corpo com o padrão visual ao redor.

É como se fosse um jogo de "Onde está o Wally?" ao contrário.

Em vez de usar uma roupa vermelha pra se destacar, o soldado usa uma roupa que dissolve a silhueta dele no fundo da cena. Seus olhos estão vendo tudo, mas o cérebro olha pra aquilo e diz: "não tem nada de interessante aqui, é tudo mato".

💡 Curiosidade extra: Essa mesma lógica de "o olho capta, mas o cérebro não identifica" é usada em compressão de imagem! Os CODECs exploram o fato de que nosso cérebro não percebe certas diferenças sutis de cor pra remover dados que você nunca vai notar que sumiram. Mas isso é assunto pra mais pra frente na jornada 😉

E sim, para quem trabalha com compresão de imagem, é certo dizer que os CODECs modernos exploram o fato de que nosso olho é mais sensível ao verde para alocar mais dados para o canal verde e comprimir mais agressivamente o azul, por exemplo.

É por isso que no espaço de cor YCbCr (que vamos estudar mais pra frente), o componente de luminância Y é calculado dando mais peso ao verde.

Dito isso, vamos sair um pouco da biologia e entrar no mundo das máquinas! 🙌 

O que é um Monitor?

É serio mesmo que você fez essa pergunta? 👀

Agora que você já sabe:

  • Que cor é luz!
  • Que o olho humano tem 3 cones (R, G, B).
  • Que todas as cores que vem vem da combinação desses 3 cones.
  • E que os monitores usam RGB para "enganar" nossos olhos.

Chegou o momento de entendermos, por de baixo dos panos, como o monitor faz isso de verdade...

Se você olhar uma tela de monitor com uma lupa (ou jogar uma gota d'água na tela do seu celular), vai ver que a tela é composta por milhões de pontinhos organizados em uma grade. Cada pontinho é chamado de pixel.

Mas calma, antes de falar do pixel, eu preciso te explicar o que tem dentro dele.

A Anatomia de um Pixel

A palavra pixel vem de "Picture Element" (elemento de imagem). Cada pixel é o menor ponto de uma imagem digital.

E aqui vem a parte mais importante: cada pixel é composto por 3 sub-pixels (ou 3 "lâmpadas" minúsculas), cada uma de uma cor:

  • 🔴 Uma vermelha (R - Red)
  • 🟢 Uma verde (G - Green)
  • 🔵 Uma azul (B - Blue)

Essas 3 "lâmpadaszinhas" ficam tão próximas umas das outras que a olho nu você não consegue distingui-las. Apenas o seu cérebro mistura essas três cores e percebe uma única cor resultante.

Mas como isso funciona na prática? Simples...

  • Se as 3 lâmpadas estão totalmente apagadas (intensidade 0) → você vê preto.
  • Se as 3 estão acesas no máximo (intensidade total) → você vê branco.
  • Se as 3 estão acesas em 50% → você vê cinza.
  • Se só a vermelha está acesa → você vê vermelho.
  • Se a vermelha e a verde estão acesas no máximo, e a azul apagada → você vê amarelo.
  • Se a verde e a azul estão acesas, e a vermelha apagada → você vê ciano (aquele azul-esverdeado).
  • Se a vermelha e a azul estão acesas, e a verde apagada → você vê magenta (rosa-roxo).

Percebe como apenas essas 3 cores estão variando a intensidade de cada uma, e o monitor consegue reproduzir milhões de cores diferentes?

Lembra de alguma coisa? É exatamente como os cones dos seus olhos funcionam 🔥

Resolução de Tela: a quantidade de Pixels

Todas as telas, desde o momento que elas foram construídas, cada uma delas possui uma resolução diferente. A mais famosa delas é a Full HD (1920x1080).

Eu me lembro que o impacto foi tão forte na industria de tecnologia, que quando essas telas chegaram, os fabricantes de peças, jogos e vídeos faziam questão de dizer que o produto deles eram FULL HD Ready, você se lembra disso?

Pois bem, começando com o Full HD, isso quer dizer que a tela em si possui 1920 pixels na horizontal × 1080 pixels na vertical = 2.073.600 pixels no total (pouco mais de 2 milhões de pixels microscópios existentes dentro dessa tela).

Já uma tela 4K possui a resolução de 3840x2160, ou seja, um total de 8.294.400 pixels (mais de 8 milhões de pixels amontoados).

Quanto mais pixels, mais detalhada é a imagem. É como um mosaico: quanto menores e mais numerosas as peças, mais realista fica o resultado final.

Vejamos uma tabela com as resoluções mais comuns que encontramos por aí:

Entende agora porque as telas de 8K costumam ser caras? Imagina o nível de tecnologia que um equipamento desses deve ter para montar essas telas, principalmente esses pixels de forma microscopia um do lado do outro 🫠

Profundidade de Cor: quantos "níveis" cada Lâmpada tem?

Beleza, agora a gente já sabe que cada pixel é formado por 3 lâmpadas minúsculas (R, G, B) e que a cor final depende da intensidade de cada uma delas.

Costuma surgir uma pergunta: quantos níveis de intensidade cada lâmpada pode ter?

Tipo, ela é como um interruptor simples que só liga e desliga? Ou é como aqueles dimmers de iluminação de quarto de hotel, que você gira e a luz vai aumentando suavemente?

A resposta depende de uma coisa chamada profundidade de cor (em inglês, bit depth). E pra entender isso, a gente precisa primeiro entender o que é um bit.

O que é um Bit?

Você provavelmente já ouviu essa palavra antes. "Minha internet é de 200 megabits", "esse jogo tem 8-bit", "processador de 64 bits"...

Mas o que é um bit de verdade?

Um bit é a menor unidade de informação que existe na computação. Ele só pode ter dois valores: 0 ou 1. Ligado ou desligado. Sim ou não. Verdadeiro ou falso.

Pense num bit como um interruptor de luz simples. Ele só tem duas posições: desligado (0) ou ligado (1), onde não existe um meio termo...

A ideia é a seguinte: quando a gente fala que cada pixel tem "3 lâmpadas" (R, G, B), cada uma dessas lâmpadas não é um interruptor simples de liga/desliga. Cada lâmpada é mais como um dimmer, ou seja, ela tem vários níveis de intensidade.

A pergunta é: quantos níveis de intensidade cada lâmpada individual tem? E a resposta, por sua vez, depende da quantidade dos bits.

Não é que existem "várias lampadinhas dentro de uma lâmpada". É que uma única lâmpada vermelha pode brilhar em vários níveis diferentes, de totalmente apagada até totalmente acesa, com vários degraus no meio, e são os bits que definem quantos degraus existem.

Agora, se você tem 1 bit, quantas combinações diferentes você consegue representar?

Simples, apenas 0 ou 1, ou seja, não aceso, ou muito aceso

E se você tiver 2 bits juntos? Aí as combinações possíveis são:

00 → combinação 1
01 → combinação 2
10 → combinação 3
11 → combinação 4

Ou seja, Com 2 bits você tem 4 combinações possíveis.

E com 3 bits?

000 → combinação 1
001 → combinação 2
010 → combinação 3
011 → combinação 4
100 → combinação 5
101 → combinação 6
110 → combinação 7
111 → combinação 8

Ou seja, com 3 bits, 8 combinações possíveis.

Percebe o padrão? A cada bit que você adiciona, o número de combinações dobra. A fórmula é simples:

Combinações = 2 elevado ao número de bits (2^n)

Tá vendo a tabelinha acima? Guarda ela, porque ela vai ser hyper importante agora!

Mas ai você pode estar se perguntando... E o que Bits têm a ver com cor?

Tudo!

Lembra que cada pixel tem 3 lâmpadas (R, G, B), e cada lâmpada pode variar de intensidade? Pois bem, a quantidade de bits que a gente usa pra controlar cada lâmpada define quantos níveis de intensidade ela vai ter.

Observação: É importante ressaltar que um pixel tem 3 lâmpadas separadas, e não apenas uma única lâmpada que assume uma cor diferente. Como consta na ilustração abaixo.

O que muda em cada lâmpada não é a cor — é a intensidade (o brilho). Cada lâmpada pode ir de totalmente apagada (0) até totalmente acesa (255), mas sempre na cor dela.

É como se os bits fossem o controle de volume de cada lâmpada. Quanto mais bits, mais "posições" o controle tem, e mais suave é a transição de apagado até totalmente aceso.

Vamos ver na prática:

Com 1 bit por canal (por lâmpada):

Cada lâmpada só tem 2 estados: totalmente apagada (0) ou totalmente acesa (1). Não tem nada no meio. É aquele interruptor de liga e desliga, sem dimmer.

Com 1 bit pra cada um dos 3 canais, as combinações totais de cores são: 2 × 2 × 2 = 8 cores. Seu pixel consegue ser: preto, vermelho, verde, azul, amarelo, ciano, magenta ou branco. E nada mais.

Sabe aqueles computadores bem antigos dos anos 80? Muitos deles trabalhavam com essa limitação. Por isso as imagens eram tão "quadronas" e com cores tão duras.

Com 4 bits por canal:

Agora cada lâmpada tem 2⁴ = 16 níveis de intensidade. Ela pode ir de totalmente apagada (nível 0) até totalmente acesa (nível 15), passando por 14 tons intermediários.

Total de cores: 16 × 16 × 16 = 4.096 cores. Já melhorou bastante! Dá pra fazer uns degradês básicos, mas ainda dá pra perceber os "degraus" entre um tom e outro se você prestar atenção.

Com 8 bits por canal:

Aqui a coisa fica boa. Cada lâmpada tem 2⁸ = 256 níveis de intensidade, indo de 0 (totalmente apagada) até 255 (totalmente acesa).

Total de cores: 256 × 256 × 256 = 16.777.216 cores. Quase 17 milhões de cores diferentes!

Isso é o que o mercado chama de True Color (cor verdadeira), e é o padrão que a maioria absoluta dos monitores e telas usa hoje em dia — seu celular, seu notebook, a TV da sala. Quando alguém fala que uma tela tem "milhões de cores", é disso que estão falando.

Com 256 níveis por canal, a transição entre tons fica tão suave que o olho humano não consegue perceber os degraus entre um nível e outro. Parece um degradê contínuo e perfeito.

Com 10 bits por canal:

Cada lâmpada tem 2¹⁰ = 1.024 níveis de intensidade.

Total de cores: 1.024 × 1.024 × 1.024 = mais de 1 bilhão de cores.

Isso é usado em monitores profissionais voltados pra edição de vídeo, fotografia e conteúdo HDR (High Dynamic Range), a diferença pro olho comum é sutil, mas pra quem trabalha com tratamento de cor, faz diferença nas áreas de sombra e degradês muito suaves.

Mas por que 8 bits virou o padrão?

Porque 8 bits (256 níveis) é o ponto ideal entre qualidade visual e economia de dados.

Com menos bits (tipo 4 ou 6), você começa a perceber aqueles efeitos de "faixas" nos degradês, como se a imagem tivesse degraus de cor em vez de transições suaves. Esse efeito tem até nome técnico: banding (ou posterização).

Com mais bits (tipo 10 ou 12), a qualidade melhora, mas o olho humano comum quase não percebe a diferença, enquanto o tamanho do arquivo cresce consideravelmente.

E tem outra razão que é mais computacional, 8 bits cabem perfeitamente em 1 byte.

E byte é a unidade básica que os computadores usam pra organizar dados na memória.

Então 8 bits por canal = 1 byte por canal = tudo se alinha certinho na arquitetura dos computadores. Não é coincidência, foi uma escolha de engenharia 🤓

🎯 O padrão que nos interessa: Para o nosso CODEC de imagens, vamos trabalhar com 8 bits por canal. Isso significa que cada canal (R, G, B) vai de 0 a 255, e cada pixel precisa de 3 bytes (8 + 8 + 8 = 24 bits) para ser representado. Esse formato é chamado de RGB24, e é ele que vamos usar como matéria-prima do nosso CODEC.

Mas espera ai... se o branding representa escadinhas, e tecnicamente eu tô vendo esse artigo em uma tela de "ultima geração" (8 bits), como eu consigo visualizar essas escadinhas? Isso deveria ser impossível, não?

Boa observação! E a resposta é simples: o que você está vendo na ilustração não é banding de verdade.

Sua tela tem 8 bits, ou seja, ela consegue exibir 256 tons de vermelho perfeitamente suaves. O que a ilustração faz é simular como ficaria se a tela tivesse menos bits.

Na faixa de "1 bit", por exemplo, a ilustração está usando apenas 2 cores (preto e vermelho puro) e esticando cada uma pra ocupar metade da barra. Mas cada uma dessas 2 cores ainda está sendo renderizada pelos 256 níveis da sua tela... acontece que a gente só escolheu usar 2 deles.

É como se você tivesse um piano com 88 teclas (8 bits), mas na demonstração de "1 bit" eu pedisse pra você tocar usando apenas 2 teclas: a mais grave e a mais aguda.

O piano continua tendo 88 teclas, você só está ignorando as outras 86 de propósito pra mostrar como ficaria limitado.

Agora, se você quisesse ver banding real, teria que pegar um monitor antigo de 4 bits ou alterar as configurações de cor do sistema operacional pra forçar uma profundidade menor.

Aí sim os degradês ficariam com degraus de verdade, porque a tela fisicamente não teria níveis intermediários pra exibir.

💡 Resumindo: a ilustração é uma simulação feita em software. Ela usa poucos tons de propósito pra te dar a ideia visual do efeito. Sua tela de 8 bits está representando tudo perfeitamente, inclusive representando perfeitamente como ficaria ruim se ela tivesse menos bits 😄

O Modelo RGB: Representando Cores com Números

Agora chegamos ao ponto onde a "biologia" encontra a "computação".

No modelo RGB com 8 bits por canal, qualquer cor visível pode ser representada por 3 números entre 0 e 255:

(R, G, B)

Onde:

  • R (Red/Vermelho) = de 0 (sem vermelho) a 255 (vermelho máximo)
  • G (Green/Verde) = de 0 (sem verde) a 255 (verde máximo)
  • B (Blue/Azul) = de 0 (sem azul) a 255 (azul máximo)

Vejamos alguns exemplos na tabela abaixo:

Se você já mexeu com CSS ou HTML, provavelmente já viu as cores representadas em hexadecimal, como #FF0000 (vermelho), #00FF00 (verde), #0000FF (azul). Isso é a mesma coisa! FF em hexadecimal = 255 em decimal.  

#FF0000  →  R=255, G=0, B=0  →  Vermelho
#00FF00  →  R=0, G=255, B=0  →  Verde
#FFFFFF  →  R=255, G=255, B=255  →  Branco
#808080  →  R=128, G=128, B=128  →  Cinza

É importante observar que esse formato hexadecimal é convertido para RGB quando chega em seu monitor. Até porque o monitor não sabe ler hexadecimal, somente decimal.

Se é assim, porque a escrita em hexadecimal foi criada?

Da mesma forma que linguagens como PHP, Javascript, GoLang existem... imagina você programar diretamente em Assembly digitando muito mais linhas de código, ou pior... diretamente informando 0s e 1s ao processador 🫣

Observação importante: Eu mencionei que o monitor lê RGB pra ficar mais fácil de entender, enquanto na verdade, ele trabalha com sinais elétricos analógicos (nos monitores mais antigos, via VGA) ou sinais digitais binários (nos modernos, via HDMI/DisplayPort). No final das contas, tudo vira tensão elétrica que controla a intensidade de cada sub-pixel.

Decimal, hexadecimal, binário... são apenas formas diferentes de escrever o mesmo número para nós humanos. O computador internamente só entende binário (0s e 1s).

O valor 255, FF e 11111111 são a mesma coisa só muda a representação:

Por exemplo, vamos supor que a nível de software você quisesse escrever a cor branca que em RGA é 255, 255, 255. Se você fosse fazer isso em binário, teria que digitar:

11111111 11111111 11111111

São 24 dígitos. Imagina ter que digitar isso toda vez que quiser definir uma cor no CSS? Ia ser um pesadelo 😴

O Formato RGB24: a imagem "crua"

Certo, agora que você já entende de forma mais profunda o funcionamento do modelo RGB, vamos finalmente falar do nosso foco desta jornada, o RGB24, o formato de imagem mais bruto e "cru" que existe.

Ele é o formato onde cada pixel da imagem é armazenado como 3 bytes sequenciais: um para R, um para G e um para B. São 24 bits por pixel (8 bits × 3 canais = 24 bits). Daí o nome: RGB24.

O arquivo não tem nenhum header (cabeçalho). Não tem informação de largura, altura, profundidade de cor, nome do autor, data de criação. Nada. É só pixel atrás de pixel, byte atrás de byte.

E sim, CODECs como PNG, BMP, JPEG, JPG dentro daqueles arquivos de imagem, além das informações de cor (comprimida) eles levam alguns dados a mais como:

  • Largura e altura da imagem (pra saber quantos pixels tem em cada direção)
  • Profundidade de cor (quantos bits por canal, geralmente 8)
  • Tipo de compressão usado (pra saber como descomprimir)
  • Espaço de cor (RGB, CMYK, Grayscale...)
  • Metadados EXIF (no caso de fotos tiradas pelo celular: modelo da câmera, data e hora, localização GPS, abertura da lente, ISO...)
  • Thumbnail (uma miniatura da imagem embutida dentro do próprio arquivo, pra pré-visualização rápida)
  • Perfil ICC (informações de calibração de cor do monitor/câmera)

Tudo isso fica armazenado no que chamamos de header (cabeçalho) do arquivo, que vem antes dos dados de pixel propriamente ditos.

É como se fosse um pacote dos Correios: o header é a etiqueta colada na caixa (com remetente, destinatário, peso, dimensões), e os dados de pixel são o conteúdo que está dentro da caixa.

O RGB24 é como se alguém jogasse o conteúdo da caixa no chão sem etiqueta nenhuma. O conteúdo está lá, inteiro, mas ninguém sabe de onde veio, qual o tamanho, ou como organizar. Você precisa adivinhar (ou alguém precisa te contar) a largura e altura pra conseguir montar a imagem de volta.

💡 Dica Futura: E é exatamente por isso que quando formos criarmos o nosso CODEC autoral mais pra frente, a primeira coisa que vamos projetar é o header do nosso formato, ou seja, um cabeçalho que diga, no mínimo: "essa imagem tem X de largura, Y de altura, e foi comprimida com o método Z". Porque sem isso, ninguém consegue abrir o arquivo.

Mas dai você pode estar se perguntando... como será que o RGB24 funciona na prática?

Imagine uma imagem minúscula de 3×2 pixels (3 colunas, 2 linhas), com esse esquema de cores:

[Vermelho]  [Verde]     [Azul]
[Branco]    [Preto]     [Amarelo]

No formato RGB24, o arquivo seria uma sequência de bytes escritos dessa forma:

Linha 1:
FF 00 00    → Pixel (0,0) = Vermelho (R=255, G=0, B=0)
00 FF 00    → Pixel (1,0) = Verde    (R=0, G=255, B=0)
00 00 FF    → Pixel (2,0) = Azul     (R=0, G=0, B=255)

Linha 2:
FF FF FF    → Pixel (0,1) = Branco   (R=255, G=255, B=255)
00 00 00    → Pixel (1,1) = Preto    (R=0, G=0, B=0)
FF FF 00    → Pixel (2,1) = Amarelo  (R=255, G=255, B=0)

O arquivo inteiro teria: 3 pixels × 2 linhas × 3 bytes = 18 bytes. E só, nenhuma outra informação a mais rs

Perai, se o RGB24 é um arquivo simples de bytes, eu consigo abrir o bloco de notas aqui da máquina e escrever os bytes de forma direta, de modo a gerar uma imagem?

Sim, e não...

O pensamento faz sentido, porém, o o Bloco de Notas não funciona pra isso ❌

 Quando você digita FF 00 00 no Bloco de Notas e salva, o que fica gravado no arquivo não é o byte 0xFF (255 em decimal).

O que fica gravado são os caracteres de texto "F", "F", " ", "0", "0", " ", "0", "0", ou seja, cada um como um byte diferente representando a letra/número em ASCII.  

Ou seja, o Bloco de Notas salva texto, e um arquivo RGB24 é composto de bytes binários puros, não de texto. Então salvar no bloco de notas e esperar que saia uma imagem, não vai funcionar, mesmo que voce mude a extenção do arquivo...

É a mesma diferença entre:

  • Escrever a palavra "duzentos e cinquenta e cinco" num papel (texto)
  • Colocar 255 bolinhas dentro de um pote (dado binário)

Nesse caso, o nosso computador precisa das "bolinhas no pote", e não da palavra escrita.

Entretanto, é possível sim escrever bytes de forma direta usando o seu computador, e veremos isso futuramente em próximos tópicos.

Calculando o tamanho de uma imagem RGB24

Agora que a gente entende a estrutura, fica fácil calcular o tamanho de qualquer imagem em RGB24:

Tamanho (bytes) = Largura × Altura × 3

Observe alguns cálculos que fizemos:

Percebe o problema quando trabalhamos diretamente com uma imagem crua? Uma única imagem Full HD em RGB24 ocupa quase 6 MB! Uma 4K ocupa quase 24 MB!

E é por isso que existem CODECs de imagem como JPEG, PNG, WebP... eles existem justamente para comprimir esses dados e reduzir o tamanho do arquivo, mantendo a qualidade visual o mais próximo possível do original.

Por exemplo, se convertéssemos cada uma dessas imagens RGB24 para JPEG/PNG/WEBP teríamos o seguinte tamanho:

Percebe a diferença absurda?

Uma imagem Full HD que ocupa 5,9 MB em RGB24 cru, quando passa pelo CODEC do JPEG com qualidade 80, cai pra apenas ~185 KB.

Isso é uma redução de mais de 96%! O WebP é ainda mais agressivo, entregando ~132 KB com qualidade visual equivalente.

Mas peraí William, por que o PNG é tão maior que o JPEG?

Porque o PNG usa compressão lossless (sem perda). Ele preserva cada pixel exatamente como estava no original, sem jogar nenhum dado fora. Isso é ótimo pra manter qualidade perfeita, mas o preço é um arquivo bem maior.

Já o JPEG e o WebP usam compressão lossy (com perda). Eles analisam a imagem e jogam fora informações que o olho humano dificilmente percebe. O resultado é um arquivo muito menor, mas se você der zoom pesado ou comprimir demais, começa a aparecer aqueles "quadradinhos" borrados que a gente chama de artefatos de compressão.

O problema do RGB24: ele não tem "identidade"

Tem um detalhe importante que você precisa entender sobre o RGB24: como o arquivo não tem nenhum header (cabeçalho), você não consegue abrir ele sem saber as dimensões do mesmo.

Isso quer dizer, que se alguém te mandar um arquivo chamado foto.rgb de 921.600 bytes, automaticamente, você sabe que o arquivo tem 307.200 pixels (921.600 ÷ 3). Mas isso pode ser:

  • Uma imagem de 640 × 480 (VGA)
  • Uma imagem de 480 × 640 (VGA em pé)
  • Uma imagem de 320 × 960
  • Uma imagem de 1280 × 240

Ou seja, recebendo apenas o arquivo bruto em mãos sem nenhuma referencia de largura e altura, é impossível reconstruir a imagem corretamente.

É por isso que formatos como BMP, PNG e JPEG incluem um header no início do arquivo, para informar ao programa que vai abrir a imagem quais são as dimensões, a profundidade de cor, e outras informações necessárias.

E é por isso também que quando o nosso CODEC autoral comprimir a imagem, ele vai precisar incluir um header no arquivo de saída, dizendo pelo menos: "essa imagem tem X de largura, Y de altura, e foi comprimida com o método Z".

Convertendo imagens para RGB24 na prática

Agora sim, chegou o momento de fazermos algumas brincadeiras bem legais usando o RGB24 🤩

Sendo assim,  você vai precisar de uma ferramenta que converta imagens "normais" (PNG, JPEG) para o formato bruto.

Existem duas ferramentas principais que podemos usar para isso:

Supondo que você tenha uma imagem chamada de input.png, você pode usar o ImageMagick da seguinte forma:

# Converter PNG para RGB24 bruto
magick input.png -depth 8 RGB:output.rgb

# Converter de volta para PNG (precisa informar o tamanho!)
magick -size 800x600 -depth 8 RGB:output.rgb resultado.png

Agora, se você quiser fazer isso com o FFmpeg, use o comando:

# Converter PNG para RGB24 bruto
ffmpeg -i input.png -f rawvideo -pix_fmt rgb24 output.rgb

# Converter de volta para PNG
ffmpeg -f rawvideo -pix_fmt rgb24 -s 800x600 -i output.rgb resultado.png

Conhecendo o formato PPM: o RGB24 com "etiqueta"

Antes de aprendermos a escrever bytes de forma direta usando C (pois ainda estou devendo isso a você). 

Eu gostaria de apresentar um formato o PPM (Portable Pixmap).

O PPM é quase idêntico, é basicamente um RGB24 com um mini-header de 3 linhas que diz o formato, as dimensões e o valor máximo.

Assim a gente não precisa ficar passando largura e altura como parâmetro toda hora. Veja como o PMM se parece:

P6
800 600
255
[bytes RGB crus aqui...]

Onde:

  • P6 indica que é um PPM binário
  • 800 600 é a largura e a altura
  • 255 é o valor máximo por canal
  • Depois disso vem os pixels RGB24 em sequência, igualzinho ao raw!

Para gerar um PPM usando o magick, use o seguinte código:

magick input.png output.ppm

E nesta jornada? Vamos usar PPM pra facilitar nossa vida?

Não. A gente vai trabalhar direto com RGB24 puro. Sem etiqueta, sem header de ajuda, sem muleta. Do mesmo jeito que os CODECs de verdade fazem ⭐

Pensa comigo, quando o CODEC do JPEG recebe uma imagem pra comprimir, ele não recebe um PPM bonitinho com as informações mastigadas.

Ele recebe os pixels crus na memória (um bloco gigante de bytes RGB) e precisa saber o que fazer com aquilo. O header? Isso ai é o próprio CODEC que cria o dele na hora de salvar.

O BMP faz isso. O PNG faz isso. O JPEG faz isso. E o nosso CODEC vai fazer isso também.

Sem atalho, sem facilitador. Se a gente quer entender como um CODEC funciona de verdade, a gente precisa sentir na pele o que é trabalhar com dados crus. 

Como escrever bytes de forma direta usando C?

Anteriormente neste artigo levantamos uma questão de uma possível possibilidade de escrever imagens RGB32 usando o Bloco de notas. 

E como dito, esse tipo de coisa não funciona. Porém, também foi dito que é possível escrever bytes no nosso computador.

E uma das formas de se fazer isso, é usando a própria linguagem C como apoio.

Observação: neste exemplo e em toda jornada, usaremos o Linux como sistema operacional de desenvolvimento.

No Linux, podemos escrever um pequeno programa em C, compilar pelo terminal e gerar um arquivo binário real em poucos minutos.

Relembrando, no formato RGB24, cada pixel é formado por 3 bytes:

  • 1 byte para o canal R (Red / vermelho)
  • 1 byte para o canal G (Green / verde)
  • 1 byte para o canal B (Blue / azul)

Logo, um pixel vermelho puro pode ser representado assim:

255, 0, 0

Ou se preferir, temos a versão em hexadecimal:

FF 00 00

Isso significa que cada pixel ocupa exatamente 3 bytes no arquivo. Se criarmos uma imagem de 100 por 100 pixels, teremos:

100 × 100 = 10.000 pixels

E como cada pixel ocupa 3 bytes, só fazermos o calculo:

10.000 × 3 = 30.000 bytes

Ou seja, essa imagem crua terá exatamente 30.000 bytes.

Para acompanhar este exemplo, vamos assumir que você está usando uma distribuição baseada em Ubuntu, como Ubuntu, Linux Mint ou Pop!_OS.

A primeira coisa é abrir o seu terminal, e instalar o compilador C:

sudo apt update
sudo apt install build-essential -y

Esse pacote instala o gcc, que será usado para compilar nosso código.

Também vamos instalar o ffmpeg, pois ele nos ajudará a visualizar o arquivo RGB gerado futuramente:

sudo apt install ffmpeg -y

Agora vamos criar uma pasta simples para organizar o nosso experimento:

mkdir -p ~/rgb24-c
cd ~/rgb24-c

Essa pasta ficará dentro da sua home, o que facilita bastante para quem está começando.

Agora que estamos dentro da pasta no nosso terminal, crie o arquivo principal do projeto:

nano main.c

E com seu editor de códigos favorito, no meu caso estou usando o Visual Studio Code, abra esse arquivo que acabamos de criar com ele, e cole o seguinte código abaixo:

#include <stdio.h>  // Importa a biblioteca padrão de entrada e saída da linguagem C.
                    // É ela que fornece funções como printf(), fopen(), fwrite() e fclose().

int main() {        // Função principal do programa.
                    // Todo programa em C começa sua execução a partir da função main.

    FILE *f = fopen("vermelho.rgb", "wb");
    // FILE é um tipo usado pela linguagem C para representar um arquivo.
    // O ponteiro "f" vai guardar a referência do arquivo que abrirmos.
    //
    // fopen(...) significa "file open", ou seja, abrir um arquivo.
    //
    // "vermelho.rgb" é o nome do arquivo que será criado.
    //
    // "wb" significa:
    //   w = write   -> abrir para escrita
    //   b = binary  -> em modo binário
    //
    // Isso é muito importante porque queremos gravar bytes puros no disco,
    // e não texto formatado como um editor de texto faria.

    if (f == NULL) {
        // Aqui verificamos se o arquivo realmente foi aberto com sucesso.
        //
        // Se fopen(...) falhar por algum motivo
        // (por exemplo: permissão negada, pasta inexistente, disco cheio),
        // ele retorna NULL.
        //
        // NULL significa, basicamente, "nada válido".

        printf("Erro ao criar o arquivo.\n");
        // printf(...) escreve texto no terminal.
        // "\n" significa quebra de linha.

        return 1;
        // Encerra o programa informando que houve erro.
        // Em geral:
        //   return 0 -> sucesso
        //   return 1 -> erro
    }

    unsigned char pixel[3] = {255, 0, 0}; // RGB24: vermelho puro
    // Aqui criamos um array chamado "pixel".
    //
    // unsigned char é um tipo muito usado para trabalhar com bytes,
    // porque normalmente ocupa exatamente 1 byte e guarda valores de 0 a 255.
    //
    // pixel[3] significa que esse array tem 3 posições.
    //
    // Como estamos trabalhando com RGB24, cada pixel possui 3 bytes:
    //   pixel[0] = canal vermelho   (R)
    //   pixel[1] = canal verde      (G)
    //   pixel[2] = canal azul       (B)
    //
    // Os valores {255, 0, 0} significam:
    //   vermelho = 255
    //   verde    = 0
    //   azul     = 0
    //
    // Resultado: um pixel vermelho puro.
    //
    // Em hexadecimal, isso seria:
    //   FF 00 00

    int width = 100;
    // Largura da imagem: 100 pixels.

    int height = 100;
    // Altura da imagem: 100 pixels.

    for (int i = 0; i < width * height; i++) {
        // Esse laço será executado uma vez para cada pixel da imagem.
        //
        // width * height = 100 * 100 = 10.000
        //
        // Ou seja, esse for vai repetir 10.000 vezes.
        // Cada repetição grava 1 pixel vermelho no arquivo.
        //
        // A variável "i" é apenas um contador.
        // Ela começa em 0 e vai até 9.999.

        fwrite(pixel, 1, 3, f);
        // fwrite(...) grava dados binários em um arquivo.
        //
        // Parâmetros:
        //
        // 1) pixel
        //    É o endereço dos dados que queremos gravar.
        //    Nesse caso, os 3 bytes do pixel vermelho.
        //
        // 2) 1
        //    Tamanho de cada unidade em bytes.
        //    Aqui estamos dizendo: cada unidade tem 1 byte.
        //
        // 3) 3
        //    Quantidade de unidades que queremos escrever.
        //    Como cada unidade tem 1 byte, isso significa que
        //    estamos escrevendo 3 bytes no total.
        //
        // 4) f
        //    O arquivo de destino.
        //
        // Na prática, essa linha escreve:
        //   FF 00 00
        // no arquivo, uma vez por repetição do laço.
    }

    fclose(f);
    // Fecha o arquivo.
    //
    // Isso é importante porque garante que:
    // - todos os dados pendentes sejam realmente gravados no disco
    // - o sistema operacional libere corretamente o arquivo
    //
    // Sempre que abrimos um arquivo com fopen(...),
    // devemos fechá-lo depois com fclose(...).

    printf("Arquivo vermelho.rgb criado com sucesso.\n");
    // Exibe uma mensagem no terminal informando que deu tudo certo.

    return 0;
    // Encerra o programa com sucesso.
}

Com o arquivo salvo, compile o programa com o comando abaixo. Ou seja, volte ao terminal que está dentro da pasta que contém o main.c e rode:

gcc main.c -o gerar_rgb

Se não houver erros, um executável chamado gerar_rgb será criado na pasta atual. Por fim, basta executá-lo da seguinte forma:

./gerar_rgb

A saída esperada será:

Arquivo vermelho.rgb criado com sucesso.

Para conferir o arquivo que foi gerado, você pode executar o seguinte comando dentro da pasta:

ls -lh

O resultado deverá mostrar algo parecido com isso:

-rwxr-xr-x 1 usuario usuario ... gerar_rgb
-rw-r--r-- 1 usuario usuario 30K vermelho.rgb
-rw-r--r-- 1 usuario usuario ... main.c

Repare que o arquivo vermelho.rgb terá aproximadamente 30 KB, o que bate exatamente com a conta que fizemos antes:

100 × 100 × 3 = 30.000 bytes

Como esse arquivo é binário, abri-lo num editor de texto não será muito útil. O ideal é inspecionar seu conteúdo hexadecimal, e você pode fazer isso da seguinte forma:

xxd vermelho.rgb | head

Ou, se preferir:

hexdump -C vermelho.rgb | head

Ao executar um dos comandos acima, você verá a seguinte sequencia repetida como esta abaixo:

ff 00 00 ff 00 00 ff 00 00 ff 00 00 ...

Isso mostra com clareza que o arquivo contém bytes reais, e não a string "255,0,0" em formato de texto 🤗

E agora como eu faço para abrir essa imagem RGB24?

Como o arquivo vermelho.rgb não possui cabeçalho, ele não informa sozinho qual é sua largura, altura ou formato de pixel. Ele é apenas uma sequência bruta de bytes.

Por isso, ao visualizá-lo, precisamos dizer manualmente como ele deve ser interpretado. E como já temos o ffplay instalado em nosso sistema, podemos rodar o seguinte comando em nosso terminal:

ffplay -f rawvideo -pixel_format rgb24 -video_size 100x100 vermelho.rgb

Se tudo estiver correto, será aberta uma janela exibindo um quadro totalmente vermelho.

Esse ponto é extremamente importante: o arquivo cru por si só não “explica” o que ele representa. Quem o lê precisa saber exatamente como interpretar aqueles bytes.

O que esse experimento prova?

Ele prova que uma imagem digital, em sua forma mais crua nada mais é do que uma simples sequencia organizada de bytes.

Quando escrevemos isso em C:

unsigned char pixel[3] = {255, 0, 0};
fwrite(pixel, 1, 3, f);

Estamos literalmente gravando valores binários no disco, sem o uso do PNG, JPEG, bibliotecas gráficas, ou qualquer tipo de estrutura sofisticada. Apenas bytes.

E esse é justamente um dos fundamentos mais importantes da computação multimídia. Antes de existir um formato complexo, existe alguém organizando bytes. Antes de existir um codec, existe alguém entendendo como os dados podem ser escritos e lidos em baixo nível.

Resumo: do Olho ao Byte

Vamos recapitular tudo o que aprendemos neste artigo:

1) Cor não existe nos objetos, é uma interpretação do cérebro a partir da luz refletida.

2) A luz branca é composta por todas as cores do espectro visível (arco-íris).

3) Nosso olho tem cones (para cores) e bastonetes (para luminosidade no escuro).

4) Os cones se dividem em 3 tipos: sensíveis ao vermelho, verde e azul.

5) O monitor imita nosso olho usando pixels, onde cada pixel tem 3 sub-pixels (R, G, B).

6) Com 8 bits por canal, cada cor vai de 0 a 255, gerando mais de 16 milhões de cores possíveis.

7) O formato RGB24 armazena cada pixel como 3 bytes (R, G, B) sem nenhum header.

8) Uma imagem Full HD em RGB24 ocupa quase 6 MB, é por isso que precisamos de compressão (CODECs)!

A jornada é longa, mas a cada passo a gente vai entender mais uma peça do quebra-cabeça. E quando chegarmos no final, você vai olhar pra um arquivo JPEG e pensar: "eu sei exatamente o que está acontecendo lá dentro" 😄

Criadores de Conteúdo

Foto do William Lima
William Lima
Fundador da Micilini

Inventor nato, escreve conteudos de programação para o portal da micilini.

Torne-se um MIC 🤖

Mais de 100 mic's já estão conectados na plataforma.