# Function to crop an image:

    def crop_image(orig_img, cropped_min_x, cropped_max_x, cropped_min_y, cropped_max_y):
        '''Crops an image object of type <class 'bpy.types.Image'>.  For example, for a 10x10 image, 
        if you put cropped_min_x = 2 and cropped_max_x = 6,
        you would get back a cropped image with width 4, and 
        pixels ranging from the 2 to 5 in the x-coordinate
        
        Note: here y increasing as you down the image.  So, 
        if cropped_min_x and cropped_min_y are both zero, 
        you'll get the top-left of the image (as in GIMP).
        
        Returns: An image of type  <class 'bpy.types.Image'>
        '''

        num_channels=orig_img.channels
        #calculate cropped image size
        cropped_size_x = cropped_max_x - cropped_min_x
        cropped_size_y = cropped_max_y - cropped_min_y
        #original image size
        orig_size_x = orig_img.size[0]
        orig_size_y = orig_img.size[1]
         
        cropped_img = bpy.data.images.new(name="cropped_img", width=cropped_size_x, height=cropped_size_y)
         
        print("Exctracting image fragment, this could take a while...")

        #loop through each row of the cropped image grabbing the appropriate pixels from original
        #the reason for the strange limits is because of the 
        #order that Blender puts pixels into a 1-D array.
        current_cropped_row = 0
        for yy in range(orig_size_y - cropped_max_y, orig_size_y - cropped_min_y):
            #the index we start at for copying this row of pixels from the original image
            orig_start_index = (cropped_min_x + yy*orig_size_x) * num_channels
            #and to know where to stop we add the amount of pixels we must copy
            orig_end_index = orig_start_index + (cropped_size_x * num_channels)
            #the index we start at for the cropped image
            cropped_start_index = (current_cropped_row * cropped_size_x) * num_channels 
            cropped_end_index = cropped_start_index + (cropped_size_x * num_channels)
            
            #copy over pixels 
            cropped_img.pixels[cropped_start_index : cropped_end_index] = 
                                    orig_img.pixels[orig_start_index : orig_end_index]
            
            #move to the next row before restarting loop
            current_cropped_row += 1

        return cropped_img

# How this script works

The image class stores the pixel information as a one-dimensional array, and save 4 channels for RGBA (even if you load a .jpg).  For example, on this 2x2 image, I've labelled the order that the pixels appear:

![enter image description here][1]

and so if you have an image object named `my_image`, then `my_image.pixels` will be a 1-D array: [R value for pixel 0, G value for 0, B value for 0, A value for 0, R value for pixel 1, etc.].

# Example usage

    #crop image to 100x100 square
    cropped_min_x = 300
    cropped_max_x = 400
    cropped_min_y = 300
    cropped_max_y = 400 
    input_image_filepath='/home/garrett/Desktop/kjEh.jpg'

    orig_img = bpy.data.images.load(input_image_filepath)

    cropped_img = crop_image(orig_img, cropped_min_x, cropped_max_x, cropped_min_y, cropped_max_y)
     
    print("Saving new image...")
    cropped_img.filepath_raw = "/home/garrett/Desktop/myImage6.png" 
    cropped_img.file_format = 'PNG'
    cropped_img.save()
    print("Finished saving")

  [1]: https://i.sstatic.net/dA4EM.png