1

I'm working with binary PBM format. When I read it, I have an array with integers, where integers are ordinals of bytes. Each integer in array is transformed to list of 0 and 1 integers as binary representation, then I inverse this list. Pixel grid starts from 0:0, so position of the first pixel is [0:0].

I need to get pixel color if x >= 8. If x < 8, everything works great. Code for getting pixel color.

  def getpixel(self, x, y):
    '''PNMReader.getpixel(x, y) -> int

    Get pixel at coordinates [x:y] and return it as integer.'''
    if not isinstance(x, int) or not isinstance(y, int):
      raise(TypeError('both x and y must be integers'))
    if x < -1 or y < -1:
      raise(ValueError('both x and y are interpreted as in slice notation'))
    if x > (self.width-1):
      raise(ValueError('x cannot be equal or greater than width'))
    if y > (self.height-1):
      raise(ValueError('x cannot be equal or greater than height'))
    width, height = self.width, self.height
    x = (x, width-1)[x == -1]
    y = [y, height-1][y == -1]
    p = (y *height) +x
    width, height = self.width, self.height
    pixels = self._array_
    q = (8, width)[width -8 < 0]
    if x >= q:
      while x % q:
        y += 1
        x -= 1
    from pprint import pprint
    color = bitarray(pixels[y])[::-1][:q][x]
    print(color)

bitarray which you can see here is my defined function for getting bits for integer as list; self._array_ is a sequence of integers (which are just ordinals for bytes which were read from PBM).

I need to fix this function for getting pixel color if x >= 8. I can't understand how to calculate offset for x and y in such situations.

Only fast-working answers are accepted. I don't want joining all bits as 1-dimensional array, since it can be too slow if image is big (e.g. it can be 3000x5000 pixels).

I know that I could use some modules like imagemagick or freeimage, etc., but I can use only standart library (no additional modules). I need pure Python solution without bindings or non-default modules.

1 Answer 1

2

If self._array_ is an array of integers, each representing one byte of raster image data from the original image, then you can extract the bit you want using ordinary bit-manipulation techniques. Here's a detailed explanation (as requested in comments):

  1. We need the width of each row in bytes. This is the width in pixels divided by 8, except that the PBM format pads each row with up to 7 dummy pixels to make each row an exact number of bytes. So we need to divide the width by 8 and round up, which can be done using integer arithmetic like this:

    row_width = (width + 7) // 8
    
  2. Then we need to find the byte containing the pixel we want. PBM raster data is laid out in row-major order, so the pixel at (x, y) is in this byte:

    pixel_byte = self._array_[y * row_width + x // 8]
    
  3. You can extract bit number b (numbering from the right, with least significant bit numbered 0) from the integer i using the operation (i >> b) & 1 (right-shift by b bits and mask off the least significant bit). But PBM lays out its pixels in big-endian order, with the first pixel being in the most significant bit in the byte. So the bit we want is bit number 7 - x % 8:

    (pixel_byte >> (7 - x % 8)) & 1
    

That should solve your immediate problem. But it looks to me as though your code is very complex for what you are trying to do. Some comments:

  1. It's pointless calling isinstance and raising a TypeError yourself, because this will happen anyway when you try to do integer operations on the arguments.

  2. x > (self.width-1) would be better written as x >= self.width.

  3. Python's slice notation allows any negative integer, not just -1. For example:

    >>> range(10)[-7:-4]
    [3, 4, 5]
    
  4. You compute a number p but you don't use it.

  5. You import the function pprint and then don't call it.

I would write something like this:

import re

class Pbm(object):
    """
    Load a Binary Portable Bitmap (PBM) files and provide access to
    its pixels.  See <http://netpbm.sourceforge.net/doc/pbm.html>
    """
    _pbm_re = re.compile(r'''
       (P4)                     # 1. Magic number
       (?:\s+|\#.*\n)*          # Whitespace or comments
       ([0-9]+)                 # 2. Width of image in pixels
       (?:\s+|\#.*\n)*          # Whitespace or comments
       ([0-9]+)                 # 3. Height of image in pixels
       (?:\#.*\n)*              # Possible comments
       \s                       # A single whitespace character
       ([\000-\377]*)           # 4. Raster image data
    ''', re.X)

    def __init__(self, f):
        m = self._pbm_re.match(f.read())
        if not m:
            raise IOError("Can't parse PBM file.")
        self.width = int(m.group(2))             # Width in pixels
        self.height = int(m.group(3))            # Height in pixels
        self.row = (self.width + 7) // 8         # Width in bytes
        self.raster = m.group(4)
        if len(self.raster) != self.height * self.row:
            raise IOError("Size of raster is {} but width x height = {}."
                          .format(len(self.raster), self.height * self.row))

    def getpixel(self, x, y):
        # Negative coordinates are treated as offsets from the end,
        # like Python's slice indexes.
        if x < 0: x += self.width
        if y < 0: y += self.height
        if x < 0 or x >= self.width or y < 0 or y >= self.height:
            raise ValueError("Coords ({},{}) are out of range (0,0)-({},{})."
                             .format(x, y, self.width - 1, self.height - 1))
        return (ord(self.raster[y * self.row + x // 8]) >> (7 - x % 8)) & 1
Sign up to request clarification or add additional context in comments.

6 Comments

You've probably missed my notice: bitarray which you can see here is my defined function for getting bits for integer as list. It is not bitarray from bitarray package.
The OP didn't ask for a critique of all of their code, so I suspect you're just showing off.
What's wrong with showing off? If the OP doesn't like the rest of my answer, they can ignore it. And so can you!
No-no, everything is OK, I like to listen to constructive critics. :-) Thank you very much, I accept it!
@GarethRees: It would be useful for some other people who may find this page if you describe what happens in self._array_[y * row_width + x // 8] >> (7 - x % 8)) & 1. Bitwise operations are hard to understand for novice. However, I don't insist.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.