There are different ways to store multi-channel images. In OpenCV usually they are stored in a packed representation - one sequence of pixels, with B, G and R stored sequentially for a given pixel. In some cases the B/G/R data is stored as three separate sequences, so all of the B pixels stored in sequence, then all of the G, then all of the R. This is often referred to as a planar representation. The way you ask your question “Doesn’t the loop cycle channel by channel for all the pixels” suggests that you believe you are operating with planar data, but I suspect you are actually working with packed data.
The assertion error you are getting makes sense. Your inner loop has a limit of (expanded_image.cols - 1) * image.channels. For a BGR image with 100 columns, this would be 297, but wyhen you try to get your output pointer, your x value is out of range for that image (which has 100 columns).
You can make this code work, but you have to understand the structure of the image, etc. You would also need another loop to handle the different color channels.
The key is to understand what you get back from new_image.ptr(y,x) - it is a pointer to the first element (channel value) for the pixel with index (y,x). The other elements for that pixel (in a multi-channel image) come next.
For example, for a BGR image:
When you call output = new_image.ptr(0,3) you will get a pointer which points to the first channel of the pixel in row 0, column 3 (the 4th pixel in the image).
So output points to the B value for that pixel (assuming a BGR image), so you can access it with output, and you can access the G value with output, and the R value with output
To access the next pixel on that row, you would use:
G: output[channels + 1]
R: output[channels + 2]
And the previous pixel would be:
B: output[0 - channels];
G: output[1 - channels];
R: output[2 - channels];
I think it is helpful to work through this to understand and learn, but for code you intend to run and use, you are much better off using the provided functions. This kind of code is very error prone, and the functions that OpenCV provides have been tested, optimized, and written to handle all of the corner cases - including the ones you haven’t thought about yet. Many of the functions are able to be parallelized, take advantage of advanced instruction sets or other hardware-specific optimizations. So unless you are doing something very specific, you are almost certainly better off using the functions that already exist.