Bitmap infinito [fechado]

10

Eu gostaria de criar um bitmap durante o tempo de execução. O bitmap deve ser escalável em todos os lados e o acesso a pixels deve ser silenciosamente eficiente.

Alguma ilustração http://img546.imageshack.us/img546/4995/maptm.jpg

Entre e após os comandos mostrados na figura, Map.setPixel () e Map.getPixel () devem definir / retornar dados salvos no bitmap.

Não espero que uma implementação seja apenas um conceito de como alocar memória de forma que o setPixel () / getPixel seja o mais rápido possível.

SecStone
fonte
O campo cinza é sempre o ponto (0,0) ou também pode ser uma outra coordenada?
Falcon
2
Mais detalhes necessários. Os pixels definidos serão esparsos? Quão lento você está disposto a fazer os extendXmétodos para tornar os setPixele getPixelmais rápidos?
Peter Taylor
11
O bitmap será grande demais para caber na memória? Quais devem ser as operações rápidas - expansão, setPixel (), getPixel ()?
11
@ Falcon: Não, há tempo suficiente disponível
SecStone
3
Estou votando para encerrar esta questão como fora de tópico, porque a pergunta depende muito da imagem incluída, que já foi excluída. Como está escrito atualmente, não faz muito sentido.

Respostas:

9

Se a extend()operação precisar ser razoavelmente rápida, um Quadtree pode ser uma boa opção; na verdade, nem exigiria operações de extensão explícitas. É certo que ele não produziria um desempenho ideal para acesso aleatório a pixels individuais, mas seu comentário diz que sua operação principal é iterativa sobre os pixels, o que um quadtree poderia fazer muito rápido, talvez quase tão rápido quanto uma implementação baseada em matriz (e mais rápido se a iteração nem sempre ocorrer da mesma maneira que a matriz é definida).

Seus requisitos realmente parecem que você está tentando implementar um autômato celular como o Jogo da Vida. Você pode dar uma olhada no Hashlife , uma maneira de desempenho extremamente alto para implementar o Jogo da Vida em uma grade infinita. Observe que ele é baseado em um Quadtree, mas faz algumas otimizações adicionais muito inteligentes com base na localidade das regras do jogo.

Michael Borgwardt
fonte
Obrigado por esta ideia! Vou fazer alguns testes e relatarei os resultados.
SecStone 30/08/11
2

A @SecStone disse que há tempo suficiente disponível para a operação de expansão; portanto, a maneira mais fácil e eficiente de armazenar os pixels é usar uma única matriz plana ou uma matriz bidimensional, pois os pixels podem ser acessados ​​em tempo constante.

Falcão
fonte
4
Eu votaria nisso se você fizer uma boa sugestão sobre como você acha que a expansão deve ser tratada.
Doc Brown
@ Doc Brown: Se houver tempo suficiente, basta mudar a matriz. Ou talvez você possa trabalhar com pedaços e uma função de tradutor para um ponto para matriz e índice de pedaços (que também é executado em tempo constante).
Falcon
2

À mão

Se a memória não é um recurso muito escasso, considero trabalhar em partes maiores.
Aqui estão alguns pseudo-códigos.

class Chunk {
    Chunk new(int size) {...}
    void setPixel(int x, int y, int value) {...}
    int getPixel(int x, int y) {...}
}

class Grid {
    Map<int, Map<Chunk>> chunks;
    Grid new(int chunkSize) {...}
    void setPixel(int x, int y, int value) {
         getChunk(x,y).setPixel(x % chunkSize, y % chunkSize, value);//actually the modulo could be right in Chunk::setPixel and getPixel for more safety
    }
    int getPixel(int x, int y) { /*along the lines of setPixel*/ }
    private Chunk getChunk(int x, int y) {
         x /= chunkSize;
         y /= chunkSize;
         Map<Chunk> row = chunks.get(y);
         if (row == null) chunks.set(y, row = new Map<Chunk>());
         Chunk ret = row.get(x);
         if (ret == null) row.set(x, ret = new Chunk(chunkSize));
         return ret;
    }
}

Essa implementação é bastante ingênua.
Por um lado, ele cria chunks no getPixel (basicamente seria bom simplesmente retornar 0 ou mais, se nenhum chunks fosse definido para essa posição). Em segundo lugar, é baseado no pressuposto de que você possui uma implementação suficientemente rápida e escalável do Map. Que eu saiba, toda linguagem decente tem uma.

Além disso, você terá que jogar com o tamanho do pedaço. Para bitmaps densos, um tamanho grande de fragmento é bom; para bitmaps esparsos, um tamanho menor de fragmento é melhor. De fato, para os muito esparsos, um "tamanho de pedaço" igual a 1 é o melhor, tornando os "pedaços" obsoletos e reduzindo a estrutura de dados a um mapa int de um mapa int de pixels.

Fora da prateleira

Outra solução pode ser procurar em algumas bibliotecas gráficas. Eles são realmente muito bons em desenhar um buffer 2D em outro. Isso significaria que você simplesmente alocaria um buffer maior e teria o original desenhado nas coordenadas correspondentes.

Como estratégia geral: ao ter um "bloco de memória que cresce dinamicamente", é uma boa idéia alocar um múltiplo dele, uma vez usado. Isso é bastante intenso na memória, mas reduz significativamente os custos de alocação e cópia . A maioria das implementações de vetores aloca duas vezes seu tamanho, quando é excedida. Portanto, especialmente se você optar pela solução pronta para uso, não estenda seu buffer apenas em 1 pixel, porque apenas um pixel foi solicitado. A memória alocada é barata. Realocar, copiar e liberar é caro.

back2dos
fonte
1

Apenas alguns conselhos:

  • Se você implementar isso como uma matriz de algum tipo integral (ou uma matriz de matrizes de ...), provavelmente deverá aumentar a matriz de backup em um número de bits / pixels por vez para evitar alterar os bits à medida que os copia. O lado negativo é que você usa mais espaço, mas a proporção de espaço desperdiçado diminui à medida que o bitmap aumenta.

  • Se você usar uma estrutura de dados baseada em mapa, poderá resolver o problema de aumentar o bitmap simplesmente realocando os argumentos das coordenadas x, y das chamadas getPixele setPixel.

  • Se você usar uma estrutura de dados baseada em mapa, precisará apenas de entradas de mapa para as "unidades". Os "zeros" podem ser indicados pela ausência de uma entrada. Isso economiza uma quantidade significativa de espaço, especialmente se o bitmap for principalmente zeros.

  • Você não precisa usar um mapa de mapas. Você pode codificar um intpar x, y como um único long. Um processo análogo pode ser usado para mapear uma matriz de matrizes para uma matriz.


Finalmente, você precisa equilibrar três coisas:

  1. o desempenho getPixele setPixel,
  2. o desempenho das extend*operações, e
  3. a utilização do espaço.
Stephen C
fonte
1

Antes de tentar qualquer outra coisa mais complicada, e a menos que você não consiga guardar tudo na memória, mantenha as coisas simples e use uma matriz bidimensional juntamente com as informações sobre a origem do seu sistema de coordenadas. Para expandi-lo, use a mesma estratégia, como, por exemplo, o C ++ std :: vector: faça uma distinção entre o tamanho real de sua matriz e a capacidade da matriz e expanda a capacidade em blocos sempre que o limite for atingido. "capacidade" aqui deve ser definida em termos de intervalos (from_x, to_x), (from_y, to_y).

Isso pode precisar de uma realocação completa da memória de tempos em tempos, mas, desde que isso não ocorra com muita frequência, pode ser rápido o suficiente para o seu objetivo (na verdade, você deve tentar / criar um perfil).

Doc Brown
fonte
1

A maneira mais rápida e absoluta de acessar pixels é uma matriz bidimensional de pixels endereçáveis ​​individualmente.

Para extensões, comece com uma implementação simples que realoque e copie sempre (já que você precisará desse código). Se a criação de perfil não indicar que você está gastando muito tempo, não há necessidade de refinar ainda mais.

Se o perfil revelar uma necessidade de manter o número de realocações baixo e você não tiver restrição de memória, considere a alocação excessiva de uma porcentagem em cada direção e armazenar um deslocamento na origem. (Por exemplo, se você iniciar um novo bitmap em 1x1 e alocar uma matriz 9x9 para retê-lo, a inicial xe as ycompensações seriam 4.) A compensação aqui é ter que fazer cálculos extras durante o acesso ao pixel para aplicar a compensação.

Se as extensões forem realmente caras, você pode tentar um ou ambos:

  • Manuseie extensões verticais e horizontais de maneira diferente. A extensão vertical de uma matriz em qualquer direção pode ser conseguida alocando um novo bloco e fazendo uma única cópia de toda a matriz existente para o deslocamento apropriado na nova memória. Compare isso com extensões horizontais, onde você deve executar essa operação uma vez por linha, porque os dados existentes não são contíguos no novo bloco.

  • Mantenha o controle da quantidade e direção de extensão mais frequentes. Use essas informações para selecionar um novo tamanho e deslocamento, o que reduzirá a probabilidade de ter que fazer uma re-alocação e cópia para qualquer extensão.

Pessoalmente, duvido que você precise de um deles, a menos que a taxa de acesso de pixel a extensão seja baixa.

Blrfl
fonte
1
  • Lado a lado de tamanho constante (por exemplo, 256x256, mas com um número infinito de lado a lado)
  • Forneça um invólucro de bitmap que permita coordenadas de pixel negativas (para dar a impressão de que uma imagem pode ser expandida em todas as quatro direções sem precisar recalcular / sincronizar as referências aos valores de coordenadas existentes)
    • A classe de bitmap real (trabalhando abaixo do wrapper), no entanto, deve suportar apenas coordenadas absolutas (não negativas).
  • Sob o wrapper, forneça acesso em nível de bloco (nível de bloco) usando E / S mapeada em memória
  • Além de Map.setPixel()e Map.getPixel()que modifica um pixel por vez, também forneça métodos que copiem e modifiquem um retângulo de pixels por vez. Isso permitirá que o chamador escolha a forma mais eficiente de acesso, dependendo das informações disponíveis para o chamador.
    • As bibliotecas comerciais também fornecem métodos de atualização: uma linha de pixels, uma coluna de pixels, atualizações de dispersão / coleta e operações de aritmética / lógica blitter em uma única etapa (para minimizar a cópia de dados).

(Não vamos votar nas respostas hilárias ...)

rwong
fonte
0

A implementação mais flexível e talvez confiável é uma lista vinculada com estruturas que fornecem coordenadas x, coordenadas y e valor de bits. Eu construiria isso primeiro e o faria funcionar.

Então, se for muito lento e / ou grande, tente as formas usuais de acelerar: matriz, matriz, bitmap, compactação, armazenamento em cache, inversão, armazenando apenas valores '1' etc.

É mais fácil fazer uma implementação correta lenta mais rapidamente do que consertar uma implementação rápida e com bugs. E ao testar sua segunda implementação 'rápida', você tem um padrão de referência para compará-lo.

E, quem sabe, você provavelmente descobrirá que a versão lenta é rápida o suficiente. Enquanto toda a estrutura se encaixa na memória, as coisas já são surpreendentemente rápidas.

Andy Canfield
fonte
3
-1: "faça funcionar antes de acelerar" não é um bom motivo para começar com a pior implementação possível. Além disso, praticamente não haverá código que não precise mudar completamente com a estrutura de dados subjacente; portanto, a iteração nesse nível é uma sugestão completamente asinina aqui.
Michael Borgwardt
É por isso que você tem uma API. A API oculta a implementação subjacente. SetValue (MyMatrix, X, Y, Valor) e GetValue (MyMatrix, X, Y) ocultam se MyMatrix é uma matriz dimanional 1 ou 2 ou uma lista vinculada ou em cache no disco ou uma tabela SQL ou qualquer outra coisa. O chamador pode precisar recompilar, mas não alterar o código.
Andy Canfield
0

Proponho a seguinte implementação em Python:

class Map(dict): pass

Tem as seguintes vantagens:

  1. Obter / definir acesso via map[(1,2)]pode ser considerado O(1).
  2. A necessidade de estender explicitamente a grade desaparece.
  3. Há pouco espaço para erros.
  4. É facilmente atualizado para 3D, se necessário.
blubb
fonte
0

Se você realmente precisa de um bitmap de qualquer tamanho arbitrário - e eu quero dizer algo entre 1x1 e 1000000x1000000 amd, e precisa ser expansível sob demanda ... uma maneira possível é usar um banco de dados. Pode parecer contra-intuitivo no começo, mas o que você realmente tem é um problema de armazenamento. Um banco de dados permitirá acessar pixels individuais e armazenar essencialmente qualquer quantidade de dados. Eu não quero dizer necessariamente um db SQL, btw.

Será rápido o suficiente para seus propósitos? Não posso responder, pois não há contexto aqui sobre o que você está fazendo com este bitmap. Mas, se for para exibição na tela, considere que geralmente você só precisará puxar as linhas adicionais de varredura para exibir à medida que a tela rola, e não todos os dados.

Dito isto, não posso deixar de me perguntar se você está fazendo algo errado. Talvez você deva usar gráficos baseados em vetor e rastrear entidades individuais na memória e renderizar um bitmap apenas do tamanho necessário para a tela?

GrandmasterB
fonte
Talvez um servidor de mapas da OSGeo.
rwong
0

Aqui estão as etapas para fazê-lo funcionar corretamente:

  1. use o encaminhamento de uma interface para outra para criar uma árvore de objetos que juntos representam o bitmap
  2. toda "extensão" do bitmap alocará sua própria memória e fornecerá outra interface para ele
  3. exemplo de implementação seria:

    template<class T, class P>
    class ExtendBottom {
    public:
       ExtendBottom(T &t, int count) : t(t), count(count),k(t.XSize(), count) { }
       P &At(int x, int y) const { if (y<t.YSize()) return t.At(x,y); else return k.At(x, y-t.YSize()); }
       int XSize() const { return t.XSize(); }
       int YSize() const { return t.YSize()+count; }
    private:
       T &t;
       int count;
       MemoryBitmap k;
    };
    

Obviamente, para uma implementação real, o XSize () e o YSize () não funcionariam, mas você precisará de MinX (), MaxX (), MinY (), MaxY () ou algo parecido para manter os números de índice consistentes.

tp1
fonte