I did a quick sketch to illustrate the idea I had.
As you will see there are some artifacts in the renders and they are due to my implementation, but I don’t have the time to find the mistakes tonight.
The key idea is to use a quad tree to split the space into quads. Since you want smaller pixels in the center and bigger ones on the outside, you can generate a bunch of random points following the distribution that you want and add those point to your quad tree to split accordingly.
For the points distribution, I used this technique. Below is the code sample for the points distribution and an exemple result:
void setup() {
  size(600, 600);
  background(20);
  
  for (long i = 0; i < 100000; i++) {
    float r, a;
    r = pickRandom() * width / sqrt(2);
    a = random(TWO_PI);
    PVector pt = new PVector(width / 2.0 + r * cos(a), height / 2.0 + r * sin(a));
    stroke(230);
    point(pt.x, pt.y);
  }
}
float pickRandom() {
  float r1, p, r2;
  
  while(true) {
    r1 = random(1);
    p = 1 - pow(r1, 6);
    r2 = random(1);
    if (r2 < p) {
      return r1;
    }
  }
}
Now, if we add those points into the quad tree and look at the partial distribution we have a result similar to this:
All is left to do is replace each quad by a rectangle colored with the image average color in that region. Which would give this:
Playing a bit with the parameters, it is also possible to get a less squary look by offsetting the creation of new quads within the tree. Something like that:
And the output:
As you can see, this is when the artifacts are appearing so there is room for improvement.
Anyway, if you want the full code, please find it below.
Code
PImage img;
TreeNode quadTree;
void setup() {
  size(600, 600); //Input the same dimension as the final image
  background(20);
  
  img = loadImage("cat600.jpg"); // Chose the image that you want to use
  quadTree = new TreeNode(new Coord(0, 0), new Coord(img.width - 1, img.height - 1)); 
  
  for (long i = 0; i < 100000; i++) {
    float r, a;
    r = pickRandom() * width / sqrt(2);
    a = random(TWO_PI);
    PVector pt = new PVector(width / 2.0 + r * cos(a), height / 2.0 + r * sin(a));
    quadTree.add(pt);
  }
  
  img.loadPixels();
  //quadTree.display();
  quadTree.displayFromPic();
}
float pickRandom() {
  float r1, p, r2;
  
  while(true) {
    r1 = random(1);
    p = 1 - pow(r1, 6);
    r2 = random(1);
    if (r2 < p) {
      return r1;
    }
  }
}
class Coord{
  public int x, y;
  
  Coord(int x, int y) {
    this.x = x;
    this.y = y;
  }
}
class AABB
{
  private Coord TL;
  private Coord BR;
  private Coord midPt;
  private boolean isDivisible;
  AABB(Coord TL, Coord BR) {
    this.TL = TL;
    this.BR = BR;
    midPt = new Coord((int)((BR.x + TL.x) / 2.0), (int)((BR.y + TL.y) / 2.0));
    isDivisible = true;
    if ((int)(BR.x - TL.x) == 1 || (int)(BR.y - TL.y) == 1)
      isDivisible = false;
  }
  public boolean contains(PVector pt) {
    if (pt.x < TL.x || pt.x >= BR.x)
      return false;
    if (pt.y < TL.y || pt.y >= BR.y)
      return false;
    return true;
  }
  public boolean isDivisible() {
    return isDivisible;
  }
  public Coord getMidPt() {
    return midPt;
  }
  public Coord TL() {
    return TL;
  }
  public Coord BR() {
    return BR;
  }
  public void display() {
    noFill();
    stroke(230);
    strokeWeight(1);
    line(TL.x, TL.y, BR.x, TL.y);
    line(TL.x, BR.y, TL.x, TL.y);
  }
  public void displayFromPic() {
    float r = 0;
    float g = 0;
    float b = 0;
    for (int i = TL.x; i <= BR.x; i++) {
      for (int j = TL.y; j <= BR.y; j++) {
        int idx = j * img.width + i;
        color col = img.pixels[idx];
        r += ((col >> 16) & 0xFF);
        g += ((col >> 8) & 0xFF);
        b += (col & 0xFF);
      }
    }
    
    float divider = (BR.x - TL.x + 1) * (BR.y - TL.y + 1);
    r /= divider;
    g /= divider;
    b /= divider;
    
    for (int i = TL.x; i <= BR.x; i++) {
      for (int j = TL.y; j <= BR.y; j++) {
        int idx = j * img.width + i;
        pixels[idx] = color(r, g, b);
      }
    }
  }
}
class TreeNode
{
  private final int NODE_CAPACITY = 15;
  private final float minRatio = 0.35;
  private final float maxRatio = 0.65;
  
  private AABB boundary;
  private ArrayList<PVector> pts;
  private TreeNode TLnode;
  private TreeNode TRnode;
  private TreeNode BLnode;
  private TreeNode BRnode;
  TreeNode (Coord TL, Coord BR) {
    boundary = new AABB(TL, BR);
    initNode();
  }
  
  
  TreeNode (int x1, int y1, int x2, int y2) {
    boundary = new AABB(new Coord(x1, y1), new Coord(x2, y2));
    initNode();
  }
  
  
  private void initNode() {
    pts = new ArrayList<PVector>();
  }
  public void add(PVector pt) {
    if (!boundary.isDivisible())
      return;
      
    if (!boundary.contains(pt))
      return;
      
    if (pts.size() < NODE_CAPACITY && TLnode == null) {
      pts.add(pt);
      return;
    }
    
    if (TLnode == null)
      subdivide();
      
    addToChildren(pt);    
  }
  
  
  private void addToChildren(PVector pt) {
    if (TLnode.contains(pt))
      TLnode.add(pt);
    if (TRnode.contains(pt))
      TRnode.add(pt);
    if (BLnode.contains(pt))
      BLnode.add(pt);
    if (BRnode.contains(pt))
      BRnode.add(pt);
  }
  
  
  private void subdivideClassic() {
    Coord TL = boundary.TL();
    Coord midPt = boundary.getMidPt();
    Coord BR = boundary.BR();
    
    TLnode = new TreeNode(TL, midPt);
    TRnode = new TreeNode(midPt.x + 1, TL.y, BR.x, midPt.y);
    BLnode = new TreeNode(TL.x, midPt.y + 1, midPt.x, BR.y);
    BRnode = new TreeNode(midPt.x + 1, midPt.y + 1, BR.x, BR.y);
    
    for (int i = pts.size() - 1; i > -1; i--) {
      addToChildren(pts.get(i));
      pts.remove(i);
    }
  }
  
  
  private void subdivide() {
    Coord TL = boundary.TL();
    Coord BR = boundary.BR();
    
    if (random(1) < 0.5) {
      int x = (int)(TL.x + random(minRatio, maxRatio) * (BR.x - TL.x));
      int y1 = (int)(TL.y + random(minRatio, maxRatio) * (BR.y - TL.y));
      int y2 = (int)(TL.y + random(minRatio, maxRatio) * (BR.y - TL.y));
      
      TLnode = new TreeNode(TL.x, TL.y, x, y1);
      TRnode = new TreeNode(x + 1, TL.y, BR.x, y2);
      BLnode = new TreeNode(TL.x, y1 + 1, x, BR.y);
      BRnode = new TreeNode(x + 1, y2 + 1, BR.x, BR.y);
    } else {
      int x1 = (int)(TL.x + random(minRatio, maxRatio) * (BR.x - TL.x));
      int x2 = (int)(TL.x + random(minRatio, maxRatio) * (BR.x - TL.x));
      int y = (int)(TL.y + random(minRatio, maxRatio) * (BR.y - TL.y));
      
      TLnode = new TreeNode(TL.x, TL.y, x1, y);
      TRnode = new TreeNode(x1 + 1, TL.y, BR.x, y);
      BLnode = new TreeNode(TL.x, y + 1, x2, BR.y);
      BRnode = new TreeNode(x2 + 1, y + 1, BR.x, BR.y);
    }
    
    for (int i = pts.size() - 1; i > -1; i--) {
      addToChildren(pts.get(i));
      pts.remove(i);
    }
  }
  
  
  public boolean contains(PVector pt) {
    return boundary.contains(pt);
  }
  
  
  public void display() {
    if (TLnode == null) {
      boundary.display();
      return;
    }
    
    TLnode.display();
    TRnode.display();
    BLnode.display();
    BRnode.display();
  }
  
  
  public void displayFromPic() {
    if (TLnode == null) {
      boundary.displayFromPic();
      return;
    }
    
    TLnode.displayFromPic();
    TRnode.displayFromPic();
    BLnode.displayFromPic();
    BRnode.displayFromPic();
  }
}
 
Edit:
An example a bit more extreme:
Edit2: Corrected the rendering issue: