Quando comecei a publicar meus próprios posts, percebi que preparar as imagens consumia muito tempo. Em vez de continuar dependendo de ferramentas online, resolvi criar minha própria aplicação em C# para converter, redimensionar e comprimir imagens em lote.
Quando comecei a publicar meus prĂłprios posts, percebi que existe uma parte do processo que parece simples, mas pode consumir bastante tempo: preparar as imagens.
Para cada conteĂșdo, normalmente preciso pensar no banner, nas imagens utilizadas dentro do artigo e, principalmente, no card que serĂĄ utilizado para divulgar a publicação.
Como estou começando nesse mercado, estou tentando seguir uma rotina organizada para publicar e divulgar os conteĂșdos nas redes. E foi justamente nesse processo que comecei a perceber um problema recorrente.
Eu precisava encontrar imagens, gerar ou solicitar imagens para os banners, adaptar os arquivos para os tamanhos necessĂĄrios e, muitas vezes, reduzir o tamanho dessas imagens antes de colocĂĄ-las no site.
No começo, utilizei ferramentas que jĂĄ estavam disponĂveis no computador, como o Paint, alĂ©m de algumas ferramentas gratuitas encontradas na internet.
O problema é que isso também começou a consumir tempo.
Em alguns serviços online, eu conseguia fazer o processamento gratuitamente, mas quando chegava a hora de baixar o arquivo apareciam exigĂȘncias de cadastro, solicitação de e-mail ou opçÔes de planos pagos.
Depois de passar por esse processo algumas vezes, pensei:
SerĂĄ que eu consigo criar uma ferramenta simples em C# para resolver pelo menos uma parte desse problema?
Foi daĂ que surgiu a ideia deste pequeno projeto.
Não criei uma ferramenta profissional de edição de imagens e nem tentei competir com softwares especializados. A ideia foi muito mais simples: criar uma aplicação de console capaz de pegar imagens de uma pasta, converter o formato, redimensionar quando necessårio, comprimir e colocar os arquivos processados em outra pasta.
E o interessante Ă© que esse pequeno projeto tambĂ©m acabou se transformando em um bom exercĂcio de C# e .NET.
O que vamos criar?
A aplicação funciona basicamente com trĂȘs pastas:
- Origem: onde ficam as imagens que serĂŁo processadas;
- Destino: onde serĂŁo colocadas as imagens convertidas;
- Processados: onde os arquivos originais serĂŁo movidos depois do processamento.
Os caminhos não ficam diretamente espalhados pelo código. Eles são definidos no arquivo appsettings.json e carregados pela aplicação.
Isso permite alterar os diretĂłrios sem precisar modificar o cĂłdigo-fonte.
Estrutura do projeto
A estrutura que utilizei Ă© relativamente pequena:
Program.csâ entrada da aplicação e interação com o usuĂĄrio;Modelos.csâ modelos utilizados para representar as opçÔes e os resultados;ConversorServico.csâ responsĂĄvel pelo processamento das imagens;appsettings.jsonâ configuração das pastas utilizadas pelo programa.
Essa separação é interessante porque evita colocar toda a lógica dentro do Main. O programa conversa com o usuårio, coleta as informaçÔes e depois entrega essas opçÔes para o serviço responsåvel pelo processamento.
Configurando as pastas no appsettings.json
Começando pela configuração, o projeto utiliza um arquivo chamado appsettings.json:
{
"Caminhos": {
"PastaOrigem": "C:\\Users\\usuario\\Documents\\ImagensParaTratar\\origem",
"PastaDestino": "C:\\Users\\usuario\\Documents\\ImagensParaTratar\\destino",
"PastaProcessados": "C:\\Users\\usuario\\Documents\\ImagensParaTratar\\Processados"
}
}
No meu arquivo original, esses caminhos apontam para as pastas que utilizo no meu computador. Aqui no artigo coloquei caminhos genĂ©ricos para que vocĂȘ possa adaptar ao seu ambiente.
A ideia é simples: qualquer imagem colocada na pasta de origem poderå ser processada pela aplicação.
Por que separar a configuração do código?
Essa Ă© uma pequena decisĂŁo que pode parecer desnecessĂĄria em um projeto tĂŁo simples, mas ajuda bastante.
Imagine que amanhĂŁ vocĂȘ queira colocar a pasta de origem em outro local. Se o caminho estiver espalhado pelo cĂłdigo, serĂĄ necessĂĄrio procurar e alterar os valores.
Com o appsettings.json, basta alterar a configuração.
No Program.cs, a aplicação carrega o arquivo utilizando o sistema de configuração do .NET:
IConfiguration config = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile(
"appsettings.json",
optional: false,
reloadOnChange: true)
.Build();
Aqui temos algumas coisas acontecendo.
ConfigurationBuilder
O ConfigurationBuilder é utilizado para construir o objeto de configuração que serå utilizado pela aplicação.
SetBasePath
O mĂ©todo SetBasePath define o diretĂłrio utilizado como referĂȘncia para localizar o arquivo de configuração.
No projeto, utilizo:
Directory.GetCurrentDirectory()
Assim, o programa procura o appsettings.json a partir do diretório atual da aplicação.
AddJsonFile
Depois informamos qual arquivo serĂĄ carregado:
.AddJsonFile(
"appsettings.json",
optional: false,
reloadOnChange: true)
O parùmetro optional: false indica que o arquivo é obrigatório para a aplicação.
Depois que a configuração é carregada, podemos buscar os valores das pastas:
OpcoesConversao opcoes = new OpcoesConversao
{
PastaOrigem =
config["Caminhos:PastaOrigem"] ?? string.Empty,
PastaDestino =
config["Caminhos:PastaDestino"] ?? string.Empty,
PastaProcessados =
config["Caminhos:PastaProcessados"] ?? string.Empty
};
Perceba a sintaxe:
Caminhos:PastaOrigem
Ela representa a propriedade PastaOrigem dentro da seção Caminhos do JSON.
O modelo de opçÔes da conversão
Para evitar passar vårias informaçÔes soltas para o serviço, criei uma classe chamada OpcoesConversao.
Ela fica no arquivo Modelos.cs.
public class OpcoesConversao
{
public string PastaOrigem { get; set; } = string.Empty;
public string PastaDestino { get; set; } = string.Empty;
public string PastaProcessados { get; set; } = string.Empty;
public FormatoImagem FormatoSaida { get; set; }
= FormatoImagem.Webp;
public int? LarguraPx { get; set; }
public int? AlturaPx { get; set; }
public bool Comprimir { get; set; }
public int Qualidade => Comprimir ? 75 : 100;
}
Essa classe representa as configuraçÔes necessårias para realizar uma conversão.
PastaOrigem, PastaDestino e PastaProcessados
Essas propriedades informam para onde a aplicação deve olhar e onde deve colocar os arquivos.
public string PastaOrigem { get; set; }
public string PastaDestino { get; set; }
public string PastaProcessados { get; set; }
A terceira propriedade Ă© particularmente interessante porque permite que o programa nĂŁo simplesmente apague ou deixe o arquivo original misturado com os arquivos que ainda precisam ser processados.
FormatoSaida
O formato de saĂda Ă© representado por um enum:
public enum FormatoImagem
{
Jpeg,
Webp,
Png
}
Portanto, a versĂŁo atual do projeto trabalha com trĂȘs formatos de saĂda: JPEG, WebP e PNG.
Largura e altura
As dimensÔes são representadas por int?:
public int? LarguraPx { get; set; }
public int? AlturaPx { get; set; }
O ponto interessante aqui Ă© o ?.
Isso significa que o valor pode ser nulo.
Na prĂĄtica, isso permite que o usuĂĄrio pressione ENTER e mantenha aquela dimensĂŁo sem informar um valor.
Essa decisĂŁo deixa o programa mais flexĂvel do que simplesmente exigir largura e altura obrigatoriamente.
CompressĂŁo e qualidade
Também temos:
public bool Comprimir { get; set; }
public int Qualidade => Comprimir ? 75 : 100;
A propriedade Qualidade Ă© calculada automaticamente.
Quando a compressão estå ativada, a qualidade utilizada é 75. Quando não estå, o valor é 100. Isso estå definido diretamente no modelo utilizado pela aplicação.
à uma solução simples e adequada para o objetivo inicial do projeto. Uma futura versão poderia permitir que o usuårio escolhesse manualmente a qualidade.
Representando o resultado do processamento
Outra classe criada no projeto Ă© ResultadoItem:
public class ResultadoItem
{
public string NomeArquivo { get; set; }
= string.Empty;
public bool Sucesso { get; set; }
public string MensagemErro { get; set; }
= string.Empty;
public long TamanhoOriginalBytes { get; set; }
public long TamanhoFinalBytes { get; set; }
}
Essa classe resolve uma necessidade importante: saber o que aconteceu com cada arquivo.
Além de informar se houve sucesso ou erro, ela guarda os tamanhos original e final do arquivo.
Com isso, o programa consegue mostrar algo como:
[OK] minha-imagem.png | 850.5 KB -> 245.3 KB (71.2% alteração)
Ou, se ocorrer algum problema:
[ERRO] minha-imagem.png: mensagem do erro
Entrando no Program.cs
Agora chegamos ao ponto de entrada da aplicação.
O método Main começa carregando a configuração:
static void Main(string[] args)
{
IConfiguration config = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile(
"appsettings.json",
optional: false,
reloadOnChange: true)
.Build();
Depois disso, as opçÔes iniciais são montadas:
OpcoesConversao opcoes = new OpcoesConversao
{
PastaOrigem =
config["Caminhos:PastaOrigem"] ?? string.Empty,
PastaDestino =
config["Caminhos:PastaDestino"] ?? string.Empty,
PastaProcessados =
config["Caminhos:PastaProcessados"] ?? string.Empty
};
O programa também exibe no console quais diretórios serão utilizados:
Console.WriteLine("=============================================");
Console.WriteLine(" CONVERSOR & COMPRESSOR DE IMAGENS C# ");
Console.WriteLine("=============================================");
Console.WriteLine("Origem : " + opcoes.PastaOrigem);
Console.WriteLine("Destino : " + opcoes.PastaDestino);
Console.WriteLine("Processados: " + opcoes.PastaProcessados);
Isso Ă© Ăștil principalmente durante os testes, porque permite confirmar rapidamente se a aplicação estĂĄ apontando para as pastas corretas.
Validando a pasta de origem
Antes de pedir qualquer informação ao usuårio, o programa verifica se a pasta de origem realmente existe:
if (!Directory.Exists(opcoes.PastaOrigem))
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(
"A pasta de origem informada no appsettings.json nĂŁo existe!");
Console.ResetColor();
Console.ReadKey();
return;
}
Essa validação evita que o programa chegue à etapa de processamento tentando acessar um diretório inexistente.
Também temos um pequeno cuidado visual: o console muda a cor do texto para vermelho quando apresenta o erro e depois volta ao padrão.
Escolhendo o formato de saĂda
Depois da validação, o programa pergunta ao usuårio qual formato deseja utilizar:
Console.Write(
"Formato de saĂda (png, webp ou jpeg): ");
string entradaFormato =
(Console.ReadLine() ?? "")
.Trim()
.ToLower();
Depois a entrada Ă© convertida para o enum:
if (entradaFormato == "png")
{
opcoes.FormatoSaida = FormatoImagem.Png;
}
else if (
entradaFormato == "jpeg" ||
entradaFormato == "jpg")
{
opcoes.FormatoSaida = FormatoImagem.Jpeg;
}
else
{
opcoes.FormatoSaida = FormatoImagem.Webp;
}
Temos aqui uma decisĂŁo simples: se o usuĂĄrio informar PNG, utilizamos PNG; se informar JPEG ou JPG, utilizamos JPEG; qualquer outra entrada acaba levando ao WebP.
Em uma futura versão, eu provavelmente melhoraria essa validação para avisar quando o usuårio informar um formato invålido, em vez de assumir WebP.
Escolhendo se a imagem serĂĄ comprimida
Em seguida, o programa pergunta:
Console.Write(
"Deseja comprimir as imagens? (S/N): ");
string entradaComprimir =
(Console.ReadLine() ?? "")
.Trim()
.ToUpper();
opcoes.Comprimir =
(entradaComprimir == "S");
O funcionamento Ă© direto: se o usuĂĄrio digitar S, a propriedade Comprimir recebe true.
Essa informação posteriormente serå utilizada pelo serviço para escolher como a imagem serå codificada.
Definindo a largura da imagem
Depois vem a largura:
Console.Write(
"Informe a LARGURA em Px " +
"(ou aperte ENTER para tamanho original): ");
string inputLargura =
Console.ReadLine() ?? "";
if (int.TryParse(inputLargura, out int larg)
&& larg > 0)
{
opcoes.LarguraPx = larg;
}
O mĂ©todo int.TryParse Ă© utilizado para tentar transformar o texto digitado em um nĂșmero inteiro.
Isso é melhor do que simplesmente utilizar int.Parse, porque uma entrada que não seja numérica não provoca imediatamente uma exceção.
AlĂ©m disso, o cĂłdigo verifica se o nĂșmero Ă© maior que zero.
Se o usuĂĄrio simplesmente pressionar ENTER, a propriedade continua nula.
Definindo a altura
A altura segue praticamente a mesma lĂłgica:
Console.Write(
"Informe a ALTURA em Px " +
"(ou aperte ENTER para tamanho original): ");
string inputAltura =
Console.ReadLine() ?? "";
if (int.TryParse(inputAltura, out int alt)
&& alt > 0)
{
opcoes.AlturaPx = alt;
}
Com isso, a aplicação jå possui todas as informaçÔes necessårias para iniciar o processamento.
Onde a conversĂŁo realmente acontece?
Aqui estĂĄ uma decisĂŁo importante na estrutura do projeto.
O Program.cs nĂŁo faz diretamente o processamento das imagens.
Ele cria uma instĂąncia de ConversorServico:
ConversorServico servico =
new ConversorServico();
var resultados =
servico.ProcessarTudo(opcoes);
Isso deixa o Program responsåvel pela interação com o usuårio, enquanto a classe ConversorServico fica responsåvel pelo trabalho pesado.
à uma separação simples, mas que facilita bastante a evolução do projeto.
Conhecendo o ConversorServico
A classe começa importando os componentes necessårios do ImageSharp:
using SixLabors.ImageSharp; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.Processing;
Também utilizamos classes do próprio .NET para trabalhar com arquivos, diretórios e listas.
O método principal é:
public ListProcessarTudo( OpcoesConversao opcoes)
Ele recebe todas as opçÔes que foram definidas no Program.cs e retorna uma lista de resultados.
Criando as pastas automaticamente
Uma das primeiras coisas feitas pelo serviço é garantir que as pastas de destino existam:
if (!Directory.Exists(opcoes.PastaDestino))
{
Directory.CreateDirectory(
opcoes.PastaDestino);
}
O mesmo acontece com a pasta de processados:
if (!string.IsNullOrEmpty(opcoes.PastaProcessados)
&& !Directory.Exists(
opcoes.PastaProcessados))
{
Directory.CreateDirectory(
opcoes.PastaProcessados);
}
Assim, o programa não depende de o usuårio criar manualmente todas as pastas antes de executar a aplicação.
Quais arquivos serĂŁo processados?
O programa define quais extensÔes são aceitas:
string[] extensoesValidas =
{
".jpg",
".jpeg",
".png",
".webp",
".bmp"
};
Depois procura os arquivos existentes na pasta de origem:
Listarquivos = Directory.GetFiles(opcoes.PastaOrigem) .Where(f => extensoesValidas.Contains( Path.GetExtension(f).ToLower())) .ToList();
Esse trecho Ă© interessante porque evita que qualquer arquivo encontrado na pasta seja tratado como imagem.
Por exemplo, um arquivo .txt colocado acidentalmente no diretĂłrio nĂŁo entra na lista de processamento.
Atualmente, a lista de entrada aceita JPG, JPEG, PNG, WebP e BMP.
Gerando um identificador de data e hora
O programa também cria um sufixo baseado na data e hora:
string sufixoDataHora =
DateTime.Now.ToString(
"_yyyyMMdd_HHmm");
Esse valor serĂĄ utilizado nos nomes dos arquivos.
Por exemplo, uma imagem chamada:
banner.png
poderĂĄ gerar algo semelhante a:
banner_20260831_1534.webp
Isso ajuda a evitar conflitos quando existem arquivos com o mesmo nome.
Processando cada imagem
O processamento acontece dentro de um foreach:
foreach (string caminho in arquivos)
{
FileInfo info =
new FileInfo(caminho);
ResultadoItem item =
new ResultadoItem
{
NomeArquivo = info.Name,
TamanhoOriginalBytes = info.Length
};
// processamento...
}
Antes de processar o arquivo, o programa jĂĄ registra seu nome e seu tamanho original.
Isso serĂĄ importante posteriormente para apresentar ao usuĂĄrio o resultado da conversĂŁo.
Definindo o nome do arquivo de saĂda
O cĂłdigo pega o nome original sem a extensĂŁo:
string nomeSemExtensao =
Path.GetFileNameWithoutExtension(
info.Name);
Depois descobre qual serĂĄ a extensĂŁo do formato escolhido:
string extensao =
ObterExtensao(opcoes.FormatoSaida);
Finalmente, cria o novo nome:
string novoNomeArquivo =
$"{nomeSemExtensao}" +
$"{sufixoDataHora}" +
$"{extensao}";
E monta o caminho completo:
string caminhoDestino =
Path.Combine(
opcoes.PastaDestino,
novoNomeArquivo);
O método ObterExtensao retorna .jpg, .png ou .webp de acordo com o formato escolhido.
Carregando a imagem com ImageSharp
Agora chegamos ao processamento propriamente dito:
using (Image imagem =
Image.Load(caminho))
{
// processamento
}
O Image.Load carrega a imagem para que possamos trabalhar com ela em memĂłria.
O using garante que o objeto seja descartado ao final do processamento daquele arquivo.
Redimensionando a imagem
O redimensionamento Ă© opcional.
O cĂłdigo verifica:
if (opcoes.LarguraPx.HasValue ||
opcoes.AlturaPx.HasValue)
{
// redimensionamento
}
Isso significa que basta o usuĂĄrio informar largura ou altura para o processo de redimensionamento ser executado.
As opçÔes são montadas assim:
ResizeOptions resizeOptions =
new ResizeOptions
{
Size = new Size(
opcoes.LarguraPx ?? 0,
opcoes.AlturaPx ?? 0),
Mode = ResizeMode.Max
};
Depois:
imagem.Mutate(x =>
x.Resize(resizeOptions));
Um detalhe importante aqui é o uso de ResizeMode.Max. A intenção é trabalhar com as dimensÔes informadas sem simplesmente distorcer a imagem para preencher obrigatoriamente os dois valores.
Esse trecho Ă© o responsĂĄvel pelo redimensionamento da imagem no projeto real.
Escolhendo o encoder
Depois do redimensionamento, precisamos informar ao ImageSharp como a imagem deverĂĄ ser salva.
Para isso, o projeto utiliza o método:
IImageEncoder encoder =
ObterEncoder(
opcoes.FormatoSaida,
opcoes.Qualidade,
opcoes.Comprimir);
Esse método centraliza a escolha do encoder.
Encoder JPEG
Para JPEG, temos:
case FormatoImagem.Jpeg:
return new JpegEncoder
{
Quality = qualidade
};
A qualidade recebida pelo encoder é aquela definida nas opçÔes da conversão.
Encoder PNG
Para PNG, o comportamento Ă© um pouco diferente:
case FormatoImagem.Png:
return new PngEncoder
{
CompressionLevel =
comprimir
? PngCompressionLevel.BestCompression
: PngCompressionLevel.NoCompression
};
Quando a opção de compressão estå ativada, o código utiliza BestCompression. Caso contrårio, utiliza NoCompression.
Isso mostra uma diferença interessante entre os formatos: o conceito de "qualidade" e o mecanismo de compressão não precisam ser tratados exatamente da mesma maneira para todos os tipos de imagem.
Encoder WebP
No WebP, o projeto utiliza:
case FormatoImagem.Webp:
default:
return new WebpEncoder
{
Quality = qualidade,
FileFormat =
comprimir
? WebpFileFormatType.Lossy
: WebpFileFormatType.Lossless
};
Aqui temos uma decisĂŁo particularmente interessante.
Quando a compressĂŁo estĂĄ ativada, o arquivo Ă© salvo como Lossy. Quando nĂŁo estĂĄ, o projeto utiliza Lossless.
Assim, a opção escolhida pelo usuårio interfere diretamente na forma como o WebP serå gerado.
Salvando a imagem
Depois que o encoder foi definido, finalmente salvamos o arquivo:
imagem.Save(
caminhoDestino,
encoder);
Ă neste ponto que a imagem processada efetivamente Ă© gravada na pasta de destino.
O fluxo completo dentro do serviço é, portanto:
- localizar a imagem;
- carregar a imagem;
- redimensionar se necessĂĄrio;
- escolher o encoder;
- salvar no formato escolhido;
- registrar o tamanho final;
- mover o arquivo original para a pasta de processados.
Essa Ă© basicamente a lĂłgica central do conversor.
Medindo o tamanho final
Depois que a imagem foi salva, o cĂłdigo cria um novo FileInfo:
FileInfo infoDestino =
new FileInfo(caminhoDestino);
item.TamanhoFinalBytes =
infoDestino.Length;
item.Sucesso = true;
Agora temos os dois valores necessĂĄrios:
- tamanho original;
- tamanho final.
Isso permite apresentar ao usuårio uma comparação do resultado.
Movendo o arquivo original
Depois de gerar a nova imagem, o arquivo original Ă© movido para a pasta de processados:
if (!string.IsNullOrEmpty(
opcoes.PastaProcessados)
&& File.Exists(caminho))
{
string nomeOriginalComData =
$"{nomeSemExtensao}" +
$"{sufixoDataHora}" +
$"{info.Extension}";
string caminhoProcessado =
Path.Combine(
opcoes.PastaProcessados,
nomeOriginalComData);
File.Move(
caminho,
caminhoProcessado);
}
Esse comportamento Ă© importante para o funcionamento em lote.
Depois que uma imagem foi processada, ela deixa de estar na pasta de origem.
Assim, se eu executar o programa novamente, as imagens que jĂĄ foram processadas nĂŁo ficam novamente na fila.
O código real utiliza exatamente essa estratégia de mover o arquivo original para a pasta configurada como PastaProcessados.
E se acontecer um erro?
Como estamos trabalhando com arquivos, imagens e diretĂłrios, erros podem acontecer.
Por isso, o processamento de cada imagem estĂĄ protegido por try/catch:
catch (Exception ex)
{
item.Sucesso = false;
item.MensagemErro = ex.Message;
}
Em vez de interromper necessariamente todo o processamento ao encontrar um problema, o erro Ă© registrado no resultado daquele arquivo.
Isso Ă© bastante Ăștil quando temos vĂĄrias imagens para processar.
Imagine, por exemplo, que existam 50 arquivos na pasta e apenas um deles apresente algum problema. O modelo de resultado permite identificar qual arquivo falhou.
A aplicação registra a mensagem de exceção em MensagemErro.
Mostrando o resultado no console
Depois que o serviço termina o processamento, o Program.cs percorre os resultados:
foreach (var item in resultados)
{
if (item.Sucesso)
{
// resultado positivo
}
else
{
// erro
}
}
Para arquivos processados com sucesso, o programa converte os tamanhos para KB:
double origKb =
item.TamanhoOriginalBytes / 1024.0;
double finalKb =
item.TamanhoFinalBytes / 1024.0;
Depois calcula a porcentagem de alteração:
double reducao =
100.0 -
((double)item.TamanhoFinalBytes /
item.TamanhoOriginalBytes * 100.0);
E finalmente mostra o resultado:
Console.WriteLine(
$"[OK] {item.NomeArquivo} | " +
$"{origKb:F1} KB -> " +
$"{finalKb:F1} KB " +
$"({reducao:F1}% alteração)");
O prĂłprio programa real utiliza esses dados para mostrar no console o tamanho antes e depois do processamento.
Um exemplo do funcionamento
Imagine que eu coloque na pasta Origem uma imagem chamada:
banner.png
Ao iniciar o programa, ele pergunta:
Formato de saĂda (png, webp ou jpeg): webp Deseja comprimir as imagens? (S/N): S Informe a LARGURA em Px (ou aperte ENTER para tamanho original): 1200 Informe a ALTURA em Px (ou aperte ENTER para tamanho original): 800
O programa então passa essas informaçÔes para o serviço.
A imagem serå carregada, redimensionada conforme as opçÔes, convertida para WebP e salva na pasta de destino.
O arquivo original serĂĄ movido para a pasta de processados.
Ao final, o console apresenta o resultado do processamento.
Por que eu preferi começar com uma aplicação de console?
Uma das coisas que considero interessantes nesse projeto Ă© justamente sua simplicidade.
Eu poderia ter começado tentando criar uma aplicação desktop completa, com botÔes, campos, seleção de arquivos e uma interface gråfica.
Mas isso aumentaria bastante a quantidade de coisas que precisariam ser resolvidas.
Como o objetivo inicial era descobrir se eu conseguia resolver o problema com C#, uma aplicação de console jå era suficiente.
Primeiro eu precisava provar a ideia.
Depois poderia pensar na interface.
Essa abordagem tambĂ©m Ă© interessante para quem estĂĄ estudando programação: muitas vezes nĂŁo precisamos começar pelo projeto mais sofisticado possĂvel.
Podemos começar pequeno, fazer funcionar e evoluir.
O que este projeto ensina sobre C#?
Apesar de ser uma aplicação pequena, ela reĂșne vĂĄrios conceitos interessantes.
- leitura de arquivos JSON;
- configuração externa da aplicação;
- classes e propriedades;
- enum;
- nullable types;
- listas;
- LINQ;
- manipulação de arquivos;
- manipulação de diretórios;
- tratamento de exceçÔes;
- processamento em lote;
- uso de bibliotecas externas;
- separação de responsabilidades;
- processamento e conversĂŁo de imagens.
Por isso, mesmo sendo um projeto pequeno, ele pode ser um exercĂcio interessante para quem estĂĄ aprendendo C#.
Uma coisa que o projeto ainda nĂŁo faz
Existe uma diferença importante entre a necessidade que motivou o projeto e a versão atual do código.
Uma das dificuldades que me levou a pensar nessa ferramenta foi justamente a necessidade de trabalhar com recortes de imagens.
Porém, a versão do código apresentada neste artigo ainda não implementa um recurso de recorte manual da imagem.
O que jĂĄ estĂĄ implementado Ă© o redimensionamento, conversĂŁo e compressĂŁo.
E isso Ă© propositalmente interessante de mostrar.
NĂO precisamos fingir que o projeto estĂĄ pronto ou que possui todos os recursos imaginados inicialmente. Ele resolveu uma parte do problema e ainda pode evoluir.
O que eu faria na prĂłxima versĂŁo?
Se eu continuasse evoluindo esse projeto, o próximo passo provavelmente seria justamente transformar essa aplicação de console em uma ferramenta mais amigåvel.
Algumas possibilidades seriam:
- criar uma interface desktop;
- adicionar seleção visual de arquivos;
- permitir arrastar e soltar imagens;
- criar um recurso visual de recorte;
- permitir processar imagens individualmente;
- criar processamento em lote mais configurĂĄvel;
- permitir escolher manualmente a qualidade;
- mostrar uma prévia da imagem;
- mostrar o tamanho original e o final;
- criar presets para banners e cards;
- criar uma versĂŁo web.
Uma ideia interessante: criar presets para o blog
Como esse projeto nasceu justamente da necessidade de preparar imagens para publicação, uma evolução que faria bastante sentido seria criar configuraçÔes prontas.
Por exemplo:
1 - Card do artigo 2 - Banner 3 - Imagem interna 4 - Imagem otimizada para Web 5 - Personalizado
Ao escolher Card do artigo, a aplicação poderia automaticamente aplicar as dimensÔes e o formato definidos para esse tipo de imagem.
Assim, eu não precisaria lembrar das configuraçÔes todas as vezes que fosse preparar uma nova imagem.
Ă exatamente esse tipo de melhoria que pode transformar um pequeno script criado para resolver uma necessidade pessoal em uma ferramenta realmente Ăștil.
E se transformåssemos isso em uma aplicação web?
Outra evolução possĂvel seria transformar o projeto em uma aplicação web.
O usuĂĄrio poderia acessar uma pĂĄgina, enviar uma ou vĂĄrias imagens, escolher o formato, definir as dimensĂ”es e selecionar o nĂvel de compressĂŁo.
O servidor faria o processamento e disponibilizaria os arquivos convertidos.
Outra possibilidade seria transformar a lĂłgica existente em uma API, permitindo que outros sistemas enviem imagens para processamento.
Ou seja, a aplicação de console atual poderia ser encarada como o primeiro estågio de um projeto muito maior.
Vale a pena criar sua prĂłpria ferramenta?
No meu caso, valeu como experiĂȘncia.
Eu estava enfrentando um problema real e repetitivo. Em vez de continuar procurando uma ferramenta gratuita na internet toda vez que precisava preparar uma imagem, resolvi testar se conseguiria criar algo prĂłprio.
O resultado não é uma ferramenta profissional de edição de imagens.
E nem precisa ser.
Ela resolve algumas tarefas especĂficas que eu precisava realizar e, principalmente, me deu uma base para continuar evoluindo.
Esse é um dos aspectos da programação que mais gosto: uma necessidade pequena pode ser o começo de um projeto interessante.
ConclusĂŁo
Criar esse conversor de imagens em C# começou como uma tentativa de resolver uma dificuldade que encontrei durante a produção dos meus prĂłprios conteĂșdos.
Eu precisava lidar constantemente com imagens, encontrar ferramentas para redimensionĂĄ-las, convertĂȘ-las e reduzir seu tamanho. Em vez de depender sempre de serviços online, resolvi experimentar uma solução prĂłpria.
A aplicação atual Ă© simples, mas jĂĄ consegue realizar um fluxo bastante Ăștil: encontrar imagens em uma pasta, redimensionĂĄ-las quando necessĂĄrio, convertĂȘ-las para JPG, PNG ou WebP, aplicar as opçÔes de compressĂŁo, salvar o resultado e mover os arquivos originais para uma pasta de processados.
Além disso, o projeto mostra como podemos separar responsabilidades em uma aplicação pequena: o Program.cs cuida da interação com o usuårio, os modelos armazenam as opçÔes e resultados e o ConversorServico concentra a lógica de processamento.
Para mim, esse é justamente o valor desse tipo de projeto. Não é necessårio criar uma aplicação gigantesca para aprender alguma coisa ou resolver um problema real.
Ăs vezes, basta olhar para uma tarefa repetitiva e perguntar:
"SerĂĄ que eu consigo automatizar isso?"
Foi exatamente essa pergunta que deu origem ao projeto.
E vocĂȘ, jĂĄ criou uma ferramenta para resolver um problema do seu dia a dia? Conte nos comentĂĄrios. Pode ser justamente uma dessas pequenas automaçÔes que acabam virando um projeto muito maior.
Desafio para vocĂȘ
Se vocĂȘ gostou da ideia, tente pegar esse projeto e evoluĂ-lo. Que tal transformar o console em uma aplicação desktop? Ou adicionar um sistema visual de recorte? Outra possibilidade Ă© criar uma versĂŁo web para processar imagens diretamente pelo navegador.
Quer mais projetos prĂĄticos de C#?
No Nerd Cult, compartilho conhecimento prĂĄtico baseado em projetos reais. Assine nossa newsletter e receba conteĂșdos sobre desenvolvimento, carreira e cultura geek.
Quer colaborar com o Nerd Cult?
Se vocĂȘ tambĂ©m criou uma ferramenta, automação ou projeto interessante para resolver um problema real, entre em contato com o Nerd Cult. Projetos simples podem render Ăłtimos conteĂșdos quando existe uma histĂłria real por trĂĄs deles.
Nerd Cult â onde o cĂłdigo encontra o rock, o cinema e a cultura geek. Porque ser nerd Ă© transformar o medo em curiosidade, e a curiosidade em poder.
#CSharp #ImageSharp #Programacao #ProjetosPraticos #NerdCult
ComentĂĄrios (0)
Nenhum comentĂĄrio ainda. Seja o primeiro a comentar!
Adicionar ComentĂĄrio