When you do any arithmetic or bit operation on a char quantity, it is silently converted to int first. This is called integer promotion. It affects short (and signed char, unsigned char, and unsigned short) as well. (Technically, if sizeof(unsigned short) == sizeof(unsigned int) then unsigned short would promote to unsigned int rather than int, but you're not likely to trip over a system with that characteristic anymore, even though it's still allowed by the C standard.)
This also happens to char and short when they are passed through the anonymous arguments to printf (or any other variadic function).
So, your code
unsigned char a4=1;
char b4=1;
printf("inverted a4 = %x\t b4= %x\n",~a4, ~b4);
is equivalent to
...
printf("inverted a4 = %x\t b4= %x\n", (int) ~(int)a4, (int) ~(int)b4);
(The cast after the ~ doesn't do anything but I have written it anyway to emphasize that conversion could happen both because of the arithmetic and because of the argument passing.)
There is no way to turn this behavior off. To get the effect you wanted, I would write
static_assert(CHAR_BIT == 8);
printf("inverted a4 = %02x\tb4 = %02x\n",
(~(unsigned int)a4) & 0xFFu,
(~(unsigned int)b4) & 0xFFu);
The difference between this and the code suggested by MikeCAT and Nick is that all bit operations are done on unsigned quantities. Some bit operations have undefined behavior when applied to signed quantities, and I can never remember exactly what the rules are, so I just avoid doing any of them to signed quantities. (The change from %x to %02x is just for aesthetics.)
Systems on which the static_assert fails are vanishingly rare nowadays, but again, still allowed by the C standard. I mostly include it to document the program's expectations. (The 0xFFu masks and %02x formatters are wrong on a system with a different value for CHAR_BIT.)